fix: handle player error transitions and clean up lint issues
- commit
- 0e08fa23a283ab8f295e7fdcf704fa07203f7d3f
- author
- brice · · 2026-04-11 06:46 UTC
- parent
- 3373d7c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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 ( | |||
| 10 | 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 | 17 | // TestDestroyStreamCallsPwStreamDestroy verifies that DestroyStream delegates | |
| 14 | 18 | // to the generated pw_stream_destroy binding (not pw_stream_disconnect). | |
| 15 | 19 | func TestDestroyStreamCallsPwStreamDestroy(t *testing.T) { | |
| @@ -28,7 +32,7 @@ func TestDestroyStreamCallsPwStreamDestroy(t *testing.T) { | |||
| 28 | 32 | pinned: make(map[unsafe.Pointer]*streamCallbackStorage), | |
| 29 | 33 | } | |
| 30 | 34 | ||
| 31 | - | fakePtr := unsafe.Pointer(uintptr(0x1234)) | |
| 35 | + | fakePtr := opaquePtr() | |
| 32 | 36 | ops.pinned[fakePtr] = &streamCallbackStorage{} | |
| 33 | 37 | ||
| 34 | 38 | ops.DestroyStream(fakePtr) | |
| @@ -73,7 +77,7 @@ func TestDestroyStreamDoubleDestroySkipsSecondCall(t *testing.T) { | |||
| 73 | 77 | pinned: make(map[unsafe.Pointer]*streamCallbackStorage), | |
| 74 | 78 | } | |
| 75 | 79 | ||
| 76 | - | fakePtr := unsafe.Pointer(uintptr(0xABCD)) | |
| 80 | + | fakePtr := opaquePtr() | |
| 77 | 81 | ops.pinned[fakePtr] = &streamCallbackStorage{} | |
| 78 | 82 | ||
| 79 | 83 | ops.DestroyStream(fakePtr) | |
| @@ -97,8 +101,9 @@ func TestDestroyStreamBookkeepingDoesNotLeak(t *testing.T) { | |||
| 97 | 101 | } | |
| 98 | 102 | ||
| 99 | 103 | 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]) | |
| 102 | 107 | ops.pinned[ptr] = &streamCallbackStorage{} | |
| 103 | 108 | ops.DestroyStream(ptr) | |
| 104 | 109 | } | |
| @@ -124,7 +129,7 @@ func TestQuitMainLoopCallsPwMainLoopQuit(t *testing.T) { | |||
| 124 | 129 | } | |
| 125 | 130 | ||
| 126 | 131 | ops := &streamOpsImpl{} | |
| 127 | - | fakeLoop := unsafe.Pointer(uintptr(0x5678)) | |
| 132 | + | fakeLoop := opaquePtr() | |
| 128 | 133 | ||
| 129 | 134 | ops.QuitMainLoop(fakeLoop) | |
| 130 | 135 | ||
| @@ -147,7 +152,7 @@ func TestConnectPlaybackStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |||
| 147 | 152 | } | |
| 148 | 153 | ||
| 149 | 154 | ops := &streamOpsImpl{} | |
| 150 | - | fakePtr := unsafe.Pointer(uintptr(0x1234)) | |
| 155 | + | fakePtr := opaquePtr() | |
| 151 | 156 | ||
| 152 | 157 | validFmt := portout.PlaybackFormat{ | |
| 153 | 158 | SampleRate: 48000, | |
| @@ -182,7 +187,7 @@ func TestSetStreamActiveReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |||
| 182 | 187 | } | |
| 183 | 188 | ||
| 184 | 189 | ops := &streamOpsImpl{} | |
| 185 | - | fakePtr := unsafe.Pointer(uintptr(0x5678)) | |
| 190 | + | fakePtr := opaquePtr() | |
| 186 | 191 | ||
| 187 | 192 | err := ops.SetStreamActive(fakePtr, true) | |
| 188 | 193 | if err == nil { | |
| @@ -212,8 +217,8 @@ func TestQueueBufferReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |||
| 212 | 217 | } | |
| 213 | 218 | ||
| 214 | 219 | ops := &streamOpsImpl{} | |
| 215 | - | fakePtr := unsafe.Pointer(uintptr(0x9ABC)) | |
| 216 | - | fakeBuf := unsafe.Pointer(uintptr(0xDEF0)) | |
| 220 | + | fakePtr := opaquePtr() | |
| 221 | + | fakeBuf := opaquePtr() | |
| 217 | 222 | ||
| 218 | 223 | err := ops.QueueBuffer(fakePtr, fakeBuf) | |
| 219 | 224 | if err == nil { | |
| @@ -243,7 +248,7 @@ func TestDisconnectStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |||
| 243 | 248 | } | |
| 244 | 249 | ||
| 245 | 250 | ops := &streamOpsImpl{} | |
| 246 | - | fakePtr := unsafe.Pointer(uintptr(0x1111)) | |
| 251 | + | fakePtr := opaquePtr() | |
| 247 | 252 | ||
| 248 | 253 | err := ops.DisconnectStream(fakePtr) | |
| 249 | 254 | if err == nil { | |
| @@ -273,7 +278,7 @@ func TestDisconnectStreamReturnsNilOnSuccess(t *testing.T) { | |||
| 273 | 278 | } | |
| 274 | 279 | ||
| 275 | 280 | ops := &streamOpsImpl{} | |
| 276 | - | fakePtr := unsafe.Pointer(uintptr(0x2222)) | |
| 281 | + | fakePtr := opaquePtr() | |
| 277 | 282 | ||
| 278 | 283 | err := ops.DisconnectStream(fakePtr) | |
| 279 | 284 | if err != nil { | |
| @@ -295,7 +300,7 @@ func TestConnectPlaybackStreamDelegatesFramesPerBufferValidation(t *testing.T) { | |||
| 295 | 300 | } | |
| 296 | 301 | ||
| 297 | 302 | ops := &streamOpsImpl{} | |
| 298 | - | fakePtr := unsafe.Pointer(uintptr(0x1234)) | |
| 303 | + | fakePtr := opaquePtr() | |
| 299 | 304 | ||
| 300 | 305 | fmt := portout.PlaybackFormat{SampleRate: 48000, Channels: 2, FramesPerBuffer: 0} | |
| 301 | 306 | err := ops.ConnectPlaybackStream(fakePtr, fmt) | |
| @@ -325,7 +330,7 @@ func TestConnectPlaybackStreamDelegatesSampleRateAndChannelsValidation(t *testin | |||
| 325 | 330 | } | |
| 326 | 331 | ||
| 327 | 332 | ops := &streamOpsImpl{} | |
| 328 | - | fakePtr := unsafe.Pointer(uintptr(0x1234)) | |
| 333 | + | fakePtr := opaquePtr() | |
| 329 | 334 | ||
| 330 | 335 | tests := []struct { | |
| 331 | 336 | name string | |
| @@ -379,7 +384,7 @@ func TestConnectPlaybackStreamAcceptsValidFormat(t *testing.T) { | |||
| 379 | 384 | } | |
| 380 | 385 | ||
| 381 | 386 | ops := &streamOpsImpl{} | |
| 382 | - | fakePtr := unsafe.Pointer(uintptr(0x5678)) | |
| 387 | + | fakePtr := opaquePtr() | |
| 383 | 388 | ||
| 384 | 389 | validFmt := portout.PlaybackFormat{ | |
| 385 | 390 | SampleRate: 48000, | |
| @@ -406,7 +411,7 @@ func TestRunMainLoopReturnsPWErrorOnNegativeResult(t *testing.T) { | |||
| 406 | 411 | } | |
| 407 | 412 | ||
| 408 | 413 | ops := &streamOpsImpl{} | |
| 409 | - | fakeLoop := unsafe.Pointer(uintptr(0xAAAA)) | |
| 414 | + | fakeLoop := opaquePtr() | |
| 410 | 415 | ||
| 411 | 416 | err := ops.RunMainLoop(fakeLoop) | |
| 412 | 417 | if err == nil { | |
| @@ -436,7 +441,7 @@ func TestRunMainLoopReturnsNilOnSuccess(t *testing.T) { | |||
| 436 | 441 | } | |
| 437 | 442 | ||
| 438 | 443 | ops := &streamOpsImpl{} | |
| 439 | - | fakeLoop := unsafe.Pointer(uintptr(0xBBBB)) | |
| 444 | + | fakeLoop := opaquePtr() | |
| 440 | 445 | ||
| 441 | 446 | err := ops.RunMainLoop(fakeLoop) | |
| 442 | 447 | if err != nil { | |
| @@ -455,8 +460,8 @@ func TestCreatePlaybackStreamUsesPWLoopFromMainLoop(t *testing.T) { | |||
| 455 | 460 | pw_stream_new_simple = origNewSimple | |
| 456 | 461 | }) | |
| 457 | 462 | ||
| 458 | - | fakeMainLoop := unsafe.Pointer(uintptr(0xAAAA)) | |
| 459 | - | fakePWLoop := unsafe.Pointer(uintptr(0xBBBB)) | |
| 463 | + | fakeMainLoop := opaquePtr() | |
| 464 | + | fakePWLoop := opaquePtr() | |
| 460 | 465 | var gotContext unsafe.Pointer | |
| 461 | 466 | ||
| 462 | 467 | pw_main_loop_get_loop = func(loop unsafe.Pointer) unsafe.Pointer { | |
| @@ -469,7 +474,7 @@ func TestCreatePlaybackStreamUsesPWLoopFromMainLoop(t *testing.T) { | |||
| 469 | 474 | pw_stream_new_simple = func(context unsafe.Pointer, name *byte, props unsafe.Pointer, events unsafe.Pointer, data unsafe.Pointer) unsafe.Pointer { | |
| 470 | 475 | gotContext = context | |
| 471 | 476 | // Return a non-nil fake stream pointer. | |
| 472 | - | return unsafe.Pointer(uintptr(0xCCCC)) | |
| 477 | + | return opaquePtr() | |
| 473 | 478 | } | |
| 474 | 479 | ||
| 475 | 480 | ops := &streamOpsImpl{ | |
| @@ -513,7 +518,7 @@ func TestCreatePlaybackStreamReturnsPWErrorWhenPWLoopIsNull(t *testing.T) { | |||
| 513 | 518 | pinned: make(map[unsafe.Pointer]*streamCallbackStorage), | |
| 514 | 519 | } | |
| 515 | 520 | ||
| 516 | - | fakeMainLoop := unsafe.Pointer(uintptr(0xAAAA)) | |
| 521 | + | fakeMainLoop := opaquePtr() | |
| 517 | 522 | _, err := ops.CreatePlaybackStream(fakeMainLoop, "test", func() {}) | |
| 518 | 523 | if err == nil { | |
| 519 | 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 | 552 | ops := &streamOpsImpl{} | |
| 548 | - | fakePtr := unsafe.Pointer(uintptr(0x5678)) | |
| 553 | + | fakePtr := opaquePtr() | |
| 549 | 554 | ||
| 550 | 555 | validFmt := portout.PlaybackFormat{ | |
| 551 | 556 | SampleRate: 48000, | |
| @@ -577,7 +582,7 @@ func TestConnectPlaybackStreamPinsParamsAfterSuccess(t *testing.T) { | |||
| 577 | 582 | } | |
| 578 | 583 | ||
| 579 | 584 | ops := &streamOpsImpl{} | |
| 580 | - | fakePtr := unsafe.Pointer(uintptr(0x5678)) | |
| 585 | + | fakePtr := opaquePtr() | |
| 581 | 586 | ||
| 582 | 587 | validFmt := portout.PlaybackFormat{ | |
| 583 | 588 | SampleRate: 48000, | |
| @@ -616,7 +621,7 @@ func TestConnectPlaybackStreamDoesNotPinParamsOnFailure(t *testing.T) { | |||
| 616 | 621 | } | |
| 617 | 622 | ||
| 618 | 623 | ops := &streamOpsImpl{} | |
| 619 | - | fakePtr := unsafe.Pointer(uintptr(0x5678)) | |
| 624 | + | fakePtr := opaquePtr() | |
| 620 | 625 | ||
| 621 | 626 | validFmt := portout.PlaybackFormat{ | |
| 622 | 627 | SampleRate: 48000, | |
| @@ -652,7 +657,7 @@ func TestDestroyStreamReleasesPinnedParams(t *testing.T) { | |||
| 652 | 657 | pinned: make(map[unsafe.Pointer]*streamCallbackStorage), | |
| 653 | 658 | } | |
| 654 | 659 | ||
| 655 | - | fakePtr := unsafe.Pointer(uintptr(0xDEAD)) | |
| 660 | + | fakePtr := opaquePtr() | |
| 656 | 661 | ops.pinned[fakePtr] = &streamCallbackStorage{} | |
| 657 | 662 | ||
| 658 | 663 | 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 delegates | 17 | // 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 = 100 | 103 | 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 string | 336 | 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 = origNewSimple | 460 | 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.Pointer | 465 | 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 = context | 475 | 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) | |||
| 115 | 115 | off += writeIdProp(buf[off:], spaFormatMediaSubtype, spaMediaSubtypeRaw) | |
| 116 | 116 | off += writeIdProp(buf[off:], spaFormatAudioFormat, spaAudioFormatF32P) | |
| 117 | 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 | 120 | cp := &connectParams{ | |
| 121 | 121 | 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 ( | |||
| 14 | 14 | var ( | |
| 15 | 15 | ErrInvalidPlayerState = errors.New("invalid player state transition") | |
| 16 | 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 | 24 | // player is the internal player shell with state machine. | |
| @@ -145,8 +150,7 @@ func (p *player) Start() error { | |||
| 145 | 150 | // Activation failed — clean up owned resources so stale pointers | |
| 146 | 151 | // are not left behind, then transition to Error. | |
| 147 | 152 | p.teardown() | |
| 148 | - | p.transition(PlayerStateError) | |
| 149 | - | return err | |
| 153 | + | return p.failWithErrorState("reactivate stream", err) | |
| 150 | 154 | } | |
| 151 | 155 | } | |
| 152 | 156 | ||
| @@ -165,16 +169,14 @@ func (p *player) startCreateResourcesAndActivate() error { | |||
| 165 | 169 | // Create main loop | |
| 166 | 170 | loopPtr, err := p.streamOps.CreateMainLoop() | |
| 167 | 171 | if err != nil { | |
| 168 | - | p.transition(PlayerStateError) | |
| 169 | - | return err | |
| 172 | + | return p.failWithErrorState("create main loop", err) | |
| 170 | 173 | } | |
| 171 | 174 | ||
| 172 | 175 | // Create playback stream | |
| 173 | 176 | streamPtr, err := p.streamOps.CreatePlaybackStream(loopPtr, "purego-pipewire-player", p.onProcess) | |
| 174 | 177 | if err != nil { | |
| 175 | 178 | p.streamOps.DestroyMainLoop(loopPtr) | |
| 176 | - | p.transition(PlayerStateError) | |
| 177 | - | return err | |
| 179 | + | return p.failWithErrorState("create playback stream", err) | |
| 178 | 180 | } | |
| 179 | 181 | ||
| 180 | 182 | // Connect stream | |
| @@ -186,8 +188,7 @@ func (p *player) startCreateResourcesAndActivate() error { | |||
| 186 | 188 | if err := p.streamOps.ConnectPlaybackStream(streamPtr, format); err != nil { | |
| 187 | 189 | p.streamOps.DestroyStream(streamPtr) | |
| 188 | 190 | p.streamOps.DestroyMainLoop(loopPtr) | |
| 189 | - | p.transition(PlayerStateError) | |
| 190 | - | return err | |
| 191 | + | return p.failWithErrorState("connect playback stream", err) | |
| 191 | 192 | } | |
| 192 | 193 | ||
| 193 | 194 | // Activate stream | |
| @@ -196,8 +197,7 @@ func (p *player) startCreateResourcesAndActivate() error { | |||
| 196 | 197 | _ = p.streamOps.DisconnectStream(streamPtr) | |
| 197 | 198 | p.streamOps.DestroyStream(streamPtr) | |
| 198 | 199 | p.streamOps.DestroyMainLoop(loopPtr) | |
| 199 | - | p.transition(PlayerStateError) | |
| 200 | - | return err | |
| 200 | + | return p.failWithErrorState("activate playback stream", err) | |
| 201 | 201 | } | |
| 202 | 202 | ||
| 203 | 203 | // All steps succeeded — store owned resources | |
| @@ -290,15 +290,17 @@ func (p *player) ensureRuntime() error { | |||
| 290 | 290 | if p.streamOps != nil { | |
| 291 | 291 | return nil | |
| 292 | 292 | } | |
| 293 | - | if err := capi.Register(); err != nil { | |
| 293 | + | if err := registerPipeWire(); err != nil { | |
| 294 | 294 | return fmt.Errorf("register pipewire: %w", err) | |
| 295 | 295 | } | |
| 296 | - | capiInstance := capi.Default() | |
| 296 | + | capiInstance := defaultPipeWireCAPI() | |
| 297 | 297 | if capiInstance == nil { | |
| 298 | 298 | return errors.New("failed to get default CAPI instance") | |
| 299 | 299 | } | |
| 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() | |
| 302 | 304 | if ops == nil { | |
| 303 | 305 | return errors.New("failed to obtain default stream operations") | |
| 304 | 306 | } | |
| @@ -306,6 +308,13 @@ func (p *player) ensureRuntime() error { | |||
| 306 | 308 | 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 | 318 | // setPaused sets the paused flag | |
| 310 | 319 | func (p *player) setPaused(paused bool) { | |
| 311 | 320 | 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 pointers | 150 | // 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 loop | 169 | // 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 stream | 175 | // 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 stream | 182 | // 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 stream | 194 | // 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 resources | 203 | // 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 nil | 291 | 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 nil | 308 | 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 flag | 318 | // 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() { | |||
| 149 | 149 | ||
| 150 | 150 | // fail transitions the player to error state and invokes OnError | |
| 151 | 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 | 155 | if p.callbacks.OnError != nil { | |
| 154 | 156 | p.callbacks.OnError(err) | |
| 155 | 157 | } | |
| @@ -149,7 +149,9 @@ func (p *player) emitDrain() { | |||||
| 149 | 149 | ||||
| 150 | // fail transitions the player to error state and invokes OnError | 150 | // 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 ( | |||
| 11 | 11 | "github.com/stretchr/testify/mock" | |
| 12 | 12 | ) | |
| 13 | 13 | ||
| 14 | + | func opaqueTestPtr() unsafe.Pointer { | |
| 15 | + | return unsafe.Pointer(new(byte)) | |
| 16 | + | } | |
| 17 | + | ||
| 14 | 18 | // TestProcessCallbackSilenceFillUnderrun tests that when Fill returns fewer frames | |
| 15 | 19 | // than requested, the remaining frames are filled with silence | |
| 16 | 20 | func TestProcessCallbackSilenceFillUnderrun(t *testing.T) { | |
| @@ -499,8 +503,8 @@ func TestPCMBufferAllocate(t *testing.T) { | |||
| 499 | 503 | // goroutine — then transitions to Playing. | |
| 500 | 504 | func TestPlayerStartCreatesLoopConnectsStreamAndRunsMainLoop(t *testing.T) { | |
| 501 | 505 | mockOps := mocks.NewMockStreamOps(t) | |
| 502 | - | fakeLoop := unsafe.Pointer(uintptr(0xCAFE)) | |
| 503 | - | fakeStream := unsafe.Pointer(uintptr(0xBEEF)) | |
| 506 | + | fakeLoop := opaqueTestPtr() | |
| 507 | + | fakeStream := opaqueTestPtr() | |
| 504 | 508 | cfg := PlayerConfig{ | |
| 505 | 509 | SampleRate: 48000, | |
| 506 | 510 | Channels: 2, | |
| @@ -542,8 +546,8 @@ func TestPlayerStartCreatesLoopConnectsStreamAndRunsMainLoop(t *testing.T) { | |||
| 542 | 546 | // queue the buffer back. | |
| 543 | 547 | func TestProcessCallbackDequeuesProcessesAndQueues(t *testing.T) { | |
| 544 | 548 | mockOps := mocks.NewMockStreamOps(t) | |
| 545 | - | fakeLoop := unsafe.Pointer(uintptr(0xCAFE)) | |
| 546 | - | fakeStream := unsafe.Pointer(uintptr(0xBEEF)) | |
| 549 | + | fakeLoop := opaqueTestPtr() | |
| 550 | + | fakeStream := opaqueTestPtr() | |
| 547 | 551 | cfg := PlayerConfig{ | |
| 548 | 552 | SampleRate: 48000, | |
| 549 | 553 | Channels: 2, | |
| @@ -635,8 +639,8 @@ func TestProcessCallbackDequeuesProcessesAndQueues(t *testing.T) { | |||
| 635 | 639 | // early when DequeueBuffer returns nil. | |
| 636 | 640 | func TestProcessCallbackNilBufferReturnsEarly(t *testing.T) { | |
| 637 | 641 | mockOps := mocks.NewMockStreamOps(t) | |
| 638 | - | fakeLoop := unsafe.Pointer(uintptr(0xCAFE)) | |
| 639 | - | fakeStream := unsafe.Pointer(uintptr(0xBEEF)) | |
| 642 | + | fakeLoop := opaqueTestPtr() | |
| 643 | + | fakeStream := opaqueTestPtr() | |
| 640 | 644 | cfg := defaultTestConfig() | |
| 641 | 645 | ||
| 642 | 646 | wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg) | |
| @@ -669,8 +673,8 @@ func TestProcessCallbackNilBufferReturnsEarly(t *testing.T) { | |||
| 669 | 673 | // QueueBuffer returns an error, onProcess routes it through p.fail(). | |
| 670 | 674 | func TestProcessCallbackQueueBufferFailsRoutesThroughFail(t *testing.T) { | |
| 671 | 675 | mockOps := mocks.NewMockStreamOps(t) | |
| 672 | - | fakeLoop := unsafe.Pointer(uintptr(0xCAFE)) | |
| 673 | - | fakeStream := unsafe.Pointer(uintptr(0xBEEF)) | |
| 676 | + | fakeLoop := opaqueTestPtr() | |
| 677 | + | fakeStream := opaqueTestPtr() | |
| 674 | 678 | cfg := PlayerConfig{ | |
| 675 | 679 | SampleRate: 48000, | |
| 676 | 680 | Channels: 2, | |
| @@ -750,8 +754,8 @@ func TestProcessCallbackQueueBufferFailsRoutesThroughFail(t *testing.T) { | |||
| 750 | 754 | // and still re-queues the buffer. | |
| 751 | 755 | func TestProcessCallbackBufferViewFailsRoutesThroughFail(t *testing.T) { | |
| 752 | 756 | mockOps := mocks.NewMockStreamOps(t) | |
| 753 | - | fakeLoop := unsafe.Pointer(uintptr(0xCAFE)) | |
| 754 | - | fakeStream := unsafe.Pointer(uintptr(0xBEEF)) | |
| 757 | + | fakeLoop := opaqueTestPtr() | |
| 758 | + | fakeStream := opaqueTestPtr() | |
| 755 | 759 | cfg := PlayerConfig{ | |
| 756 | 760 | SampleRate: 48000, | |
| 757 | 761 | Channels: 2, | |
| @@ -822,8 +826,8 @@ func TestProcessCallbackBufferViewFailsRoutesThroughFail(t *testing.T) { | |||
| 822 | 826 | // error behavior stand. | |
| 823 | 827 | func TestProcessCallbackProcessCMErrorRequeuesBuffer(t *testing.T) { | |
| 824 | 828 | mockOps := mocks.NewMockStreamOps(t) | |
| 825 | - | fakeLoop := unsafe.Pointer(uintptr(0xCAFE)) | |
| 826 | - | fakeStream := unsafe.Pointer(uintptr(0xBEEF)) | |
| 829 | + | fakeLoop := opaqueTestPtr() | |
| 830 | + | fakeStream := opaqueTestPtr() | |
| 827 | 831 | cfg := PlayerConfig{ | |
| 828 | 832 | SampleRate: 48000, | |
| 829 | 833 | Channels: 2, | |
| @@ -904,8 +908,8 @@ func TestProcessCallbackProcessCMErrorRequeuesBuffer(t *testing.T) { | |||
| 904 | 908 | // and loop are destroyed and no stale pointers are left. | |
| 905 | 909 | func TestPlayerStartFailedConnectDestroysResources(t *testing.T) { | |
| 906 | 910 | mockOps := mocks.NewMockStreamOps(t) | |
| 907 | - | fakeLoop := unsafe.Pointer(uintptr(0xCAFE)) | |
| 908 | - | fakeStream := unsafe.Pointer(uintptr(0xBEEF)) | |
| 911 | + | fakeLoop := opaqueTestPtr() | |
| 912 | + | fakeStream := opaqueTestPtr() | |
| 909 | 913 | cfg := defaultTestConfig() | |
| 910 | 914 | ||
| 911 | 915 | connectErr := errors.New("connect failed") | |
| @@ -957,8 +961,8 @@ func TestPlayerStartFailedConnectDestroysResources(t *testing.T) { | |||
| 957 | 961 | // and no stale pointers are left. | |
| 958 | 962 | func TestPlayerStartFailedActivateDestroysResources(t *testing.T) { | |
| 959 | 963 | mockOps := mocks.NewMockStreamOps(t) | |
| 960 | - | fakeLoop := unsafe.Pointer(uintptr(0xCAFE)) | |
| 961 | - | fakeStream := unsafe.Pointer(uintptr(0xBEEF)) | |
| 964 | + | fakeLoop := opaqueTestPtr() | |
| 965 | + | fakeStream := opaqueTestPtr() | |
| 962 | 966 | cfg := defaultTestConfig() | |
| 963 | 967 | ||
| 964 | 968 | activateErr := errors.New("activate failed") | |
| @@ -1011,7 +1015,7 @@ func TestPlayerStartFailedActivateDestroysResources(t *testing.T) { | |||
| 1011 | 1015 | // destroyed and no stale pointers are left. | |
| 1012 | 1016 | func TestPlayerStartFailedCreateStreamDestroysLoop(t *testing.T) { | |
| 1013 | 1017 | mockOps := mocks.NewMockStreamOps(t) | |
| 1014 | - | fakeLoop := unsafe.Pointer(uintptr(0xCAFE)) | |
| 1018 | + | fakeLoop := opaqueTestPtr() | |
| 1015 | 1019 | cfg := defaultTestConfig() | |
| 1016 | 1020 | ||
| 1017 | 1021 | createErr := errors.New("stream creation failed") | |
| @@ -1055,8 +1059,8 @@ func TestPlayerStartFailedCreateStreamDestroysLoop(t *testing.T) { | |||
| 1055 | 1059 | // config.FramesPerBuffer even when pwBuffer.Requested is smaller. | |
| 1056 | 1060 | func TestProcessCallbackUsesConfigFramesPerBuffer(t *testing.T) { | |
| 1057 | 1061 | mockOps := mocks.NewMockStreamOps(t) | |
| 1058 | - | fakeLoop := unsafe.Pointer(uintptr(0xCAFE)) | |
| 1059 | - | fakeStream := unsafe.Pointer(uintptr(0xBEEF)) | |
| 1062 | + | fakeLoop := opaqueTestPtr() | |
| 1063 | + | fakeStream := opaqueTestPtr() | |
| 1060 | 1064 | ||
| 1061 | 1065 | // Config requests 1024 frames per buffer, but PipeWire will request | |
| 1062 | 1066 | // 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 frames | 18 | // TestProcessCallbackSilenceFillUnderrun tests that when Fill returns fewer frames | ||
| 15 | // than requested, the remaining frames are filled with silence | 19 | // 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 request | 1065 | // 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 | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "errors" | |
| 5 | + | "strings" | |
| 5 | 6 | "sync" | |
| 6 | 7 | "testing" | |
| 7 | 8 | "time" | |
| @@ -62,8 +63,8 @@ func waitOnMainLoop(t *testing.T, wg *sync.WaitGroup) { | |||
| 62 | 63 | ||
| 63 | 64 | func TestPlayerStopIsRestartableButCloseIsTerminal(t *testing.T) { | |
| 64 | 65 | mockOps := mocks.NewMockStreamOps(t) | |
| 65 | - | fakeLoop := unsafe.Pointer(uintptr(0xBEEF)) | |
| 66 | - | fakeStream := unsafe.Pointer(uintptr(0xDEAD)) | |
| 66 | + | fakeLoop := opaqueTestPtr() | |
| 67 | + | fakeStream := opaqueTestPtr() | |
| 67 | 68 | cfg := defaultTestConfig() | |
| 68 | 69 | ||
| 69 | 70 | // 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 | 208 | func TestPlayerStartTransitionsThroughStartingToPlaying(t *testing.T) { | |
| 153 | 209 | mockOps := mocks.NewMockStreamOps(t) | |
| 154 | - | fakeLoop := unsafe.Pointer(uintptr(0xBEEF)) | |
| 155 | - | fakeStream := unsafe.Pointer(uintptr(0xDEAD)) | |
| 210 | + | fakeLoop := opaqueTestPtr() | |
| 211 | + | fakeStream := opaqueTestPtr() | |
| 156 | 212 | cfg := defaultTestConfig() | |
| 157 | 213 | ||
| 158 | 214 | wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg) | |
| @@ -178,8 +234,8 @@ func TestPlayerStartTransitionsThroughStartingToPlaying(t *testing.T) { | |||
| 178 | 234 | ||
| 179 | 235 | func TestPlayerPauseTransitionsToPaused(t *testing.T) { | |
| 180 | 236 | mockOps := mocks.NewMockStreamOps(t) | |
| 181 | - | fakeLoop := unsafe.Pointer(uintptr(0xBEEF)) | |
| 182 | - | fakeStream := unsafe.Pointer(uintptr(0xDEAD)) | |
| 237 | + | fakeLoop := opaqueTestPtr() | |
| 238 | + | fakeStream := opaqueTestPtr() | |
| 183 | 239 | cfg := defaultTestConfig() | |
| 184 | 240 | ||
| 185 | 241 | wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg) | |
| @@ -205,8 +261,8 @@ func TestPlayerPauseTransitionsToPaused(t *testing.T) { | |||
| 205 | 261 | ||
| 206 | 262 | func TestPlayerStopClearsPausedState(t *testing.T) { | |
| 207 | 263 | mockOps := mocks.NewMockStreamOps(t) | |
| 208 | - | fakeLoop := unsafe.Pointer(uintptr(0xBEEF)) | |
| 209 | - | fakeStream := unsafe.Pointer(uintptr(0xDEAD)) | |
| 264 | + | fakeLoop := opaqueTestPtr() | |
| 265 | + | fakeStream := opaqueTestPtr() | |
| 210 | 266 | cfg := defaultTestConfig() | |
| 211 | 267 | ||
| 212 | 268 | wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg) | |
| @@ -240,8 +296,8 @@ func TestPlayerStopClearsPausedState(t *testing.T) { | |||
| 240 | 296 | ||
| 241 | 297 | func TestPlayerCloseIsIdempotent(t *testing.T) { | |
| 242 | 298 | mockOps := mocks.NewMockStreamOps(t) | |
| 243 | - | fakeLoop := unsafe.Pointer(uintptr(0xBEEF)) | |
| 244 | - | fakeStream := unsafe.Pointer(uintptr(0xDEAD)) | |
| 299 | + | fakeLoop := opaqueTestPtr() | |
| 300 | + | fakeStream := opaqueTestPtr() | |
| 245 | 301 | cfg := defaultTestConfig() | |
| 246 | 302 | ||
| 247 | 303 | wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg) | |
| @@ -280,8 +336,8 @@ func TestPlayerCloseIsIdempotent(t *testing.T) { | |||
| 280 | 336 | // SetStreamActive(false) via StreamOps and transitions to Stopped. | |
| 281 | 337 | func TestPlayerStopDeactivatesStream(t *testing.T) { | |
| 282 | 338 | mockOps := mocks.NewMockStreamOps(t) | |
| 283 | - | fakeLoop := unsafe.Pointer(uintptr(0x1234)) | |
| 284 | - | fakeStream := unsafe.Pointer(uintptr(0x5678)) | |
| 339 | + | fakeLoop := opaqueTestPtr() | |
| 340 | + | fakeStream := opaqueTestPtr() | |
| 285 | 341 | cfg := defaultTestConfig() | |
| 286 | 342 | ||
| 287 | 343 | wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg) | |
| @@ -312,8 +368,8 @@ func TestPlayerStopDeactivatesStream(t *testing.T) { | |||
| 312 | 368 | // SetStreamActive error and leaves the player in the pre-stop state. | |
| 313 | 369 | func TestPlayerStopReturnsDeactivateError(t *testing.T) { | |
| 314 | 370 | mockOps := mocks.NewMockStreamOps(t) | |
| 315 | - | fakeLoop := unsafe.Pointer(uintptr(0x5678)) | |
| 316 | - | fakeStream := unsafe.Pointer(uintptr(0x9ABC)) | |
| 371 | + | fakeLoop := opaqueTestPtr() | |
| 372 | + | fakeStream := opaqueTestPtr() | |
| 317 | 373 | deactivateErr := errors.New("deactivate failed") | |
| 318 | 374 | cfg := defaultTestConfig() | |
| 319 | 375 | ||
| @@ -374,8 +430,8 @@ func TestPlayerStopWithNilStreamPtrIsNoop(t *testing.T) { | |||
| 374 | 430 | // when the corresponding pointers are set. | |
| 375 | 431 | func TestPlayerCloseTeardownCallsStreamOps(t *testing.T) { | |
| 376 | 432 | mockOps := mocks.NewMockStreamOps(t) | |
| 377 | - | fakeStream := unsafe.Pointer(uintptr(0xAAAA)) | |
| 378 | - | fakeLoop := unsafe.Pointer(uintptr(0xBBBB)) | |
| 433 | + | fakeStream := opaqueTestPtr() | |
| 434 | + | fakeLoop := opaqueTestPtr() | |
| 379 | 435 | ||
| 380 | 436 | // Expect teardown calls in order: DisconnectStream, DestroyStream, QuitMainLoop, DestroyMainLoop. | |
| 381 | 437 | mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil) | |
| @@ -426,8 +482,8 @@ func TestPlayerCloseWithNilPointersIsNoop(t *testing.T) { | |||
| 426 | 482 | // with teardown resources does not call StreamOps methods a second time. | |
| 427 | 483 | func TestPlayerRepeatedCloseIsSafe(t *testing.T) { | |
| 428 | 484 | mockOps := mocks.NewMockStreamOps(t) | |
| 429 | - | fakeStream := unsafe.Pointer(uintptr(0xCCCC)) | |
| 430 | - | fakeLoop := unsafe.Pointer(uintptr(0xDDDD)) | |
| 485 | + | fakeStream := opaqueTestPtr() | |
| 486 | + | fakeLoop := opaqueTestPtr() | |
| 431 | 487 | ||
| 432 | 488 | // Only expect one set of teardown calls. | |
| 433 | 489 | mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil) | |
| @@ -455,7 +511,7 @@ func TestPlayerRepeatedCloseIsSafe(t *testing.T) { | |||
| 455 | 511 | // when only streamPtr is set (no loopPtr). | |
| 456 | 512 | func TestPlayerTeardownWithOnlyStreamPtr(t *testing.T) { | |
| 457 | 513 | mockOps := mocks.NewMockStreamOps(t) | |
| 458 | - | fakeStream := unsafe.Pointer(uintptr(0xEEEE)) | |
| 514 | + | fakeStream := opaqueTestPtr() | |
| 459 | 515 | ||
| 460 | 516 | mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil) | |
| 461 | 517 | mockOps.EXPECT().DestroyStream(fakeStream).Return() | |
| @@ -477,7 +533,7 @@ func TestPlayerTeardownWithOnlyStreamPtr(t *testing.T) { | |||
| 477 | 533 | // when only loopPtr is set (no streamPtr). | |
| 478 | 534 | func TestPlayerTeardownWithOnlyLoopPtr(t *testing.T) { | |
| 479 | 535 | mockOps := mocks.NewMockStreamOps(t) | |
| 480 | - | fakeLoop := unsafe.Pointer(uintptr(0xFFFF)) | |
| 536 | + | fakeLoop := opaqueTestPtr() | |
| 481 | 537 | ||
| 482 | 538 | mockOps.EXPECT().QuitMainLoop(fakeLoop).Return() | |
| 483 | 539 | mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return() | |
| @@ -499,8 +555,8 @@ func TestPlayerTeardownWithOnlyLoopPtr(t *testing.T) { | |||
| 499 | 555 | // DisconnectStream before DestroyStream when a stream exists. | |
| 500 | 556 | func TestPlayerTeardownCallsDisconnectStream(t *testing.T) { | |
| 501 | 557 | mockOps := mocks.NewMockStreamOps(t) | |
| 502 | - | fakeStream := unsafe.Pointer(uintptr(0xAAAA)) | |
| 503 | - | fakeLoop := unsafe.Pointer(uintptr(0xBBBB)) | |
| 558 | + | fakeStream := opaqueTestPtr() | |
| 559 | + | fakeLoop := opaqueTestPtr() | |
| 504 | 560 | ||
| 505 | 561 | // Expect disconnect before destroy | |
| 506 | 562 | mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil) | |
| @@ -523,8 +579,8 @@ func TestPlayerTeardownCallsDisconnectStream(t *testing.T) { | |||
| 523 | 579 | // continues cleanup even if DisconnectStream returns an error. | |
| 524 | 580 | func TestPlayerTeardownContinuesOnDisconnectError(t *testing.T) { | |
| 525 | 581 | mockOps := mocks.NewMockStreamOps(t) | |
| 526 | - | fakeStream := unsafe.Pointer(uintptr(0xCCCC)) | |
| 527 | - | fakeLoop := unsafe.Pointer(uintptr(0xDDDD)) | |
| 582 | + | fakeStream := opaqueTestPtr() | |
| 583 | + | fakeLoop := opaqueTestPtr() | |
| 528 | 584 | disconnectErr := errors.New("disconnect failed") | |
| 529 | 585 | ||
| 530 | 586 | // Disconnect fails, but destroy should still be called | |
| @@ -550,8 +606,8 @@ func TestPlayerTeardownContinuesOnDisconnectError(t *testing.T) { | |||
| 550 | 606 | // and loop resources are cleaned up and no stale pointers remain. | |
| 551 | 607 | func TestPlayerRestartActivationFailureCleansUpResources(t *testing.T) { | |
| 552 | 608 | mockOps := mocks.NewMockStreamOps(t) | |
| 553 | - | fakeLoop := unsafe.Pointer(uintptr(0xCAFE)) | |
| 554 | - | fakeStream := unsafe.Pointer(uintptr(0xBEEF)) | |
| 609 | + | fakeLoop := opaqueTestPtr() | |
| 610 | + | fakeStream := opaqueTestPtr() | |
| 555 | 611 | cfg := defaultTestConfig() | |
| 556 | 612 | activateErr := errors.New("activate failed on restart") | |
| 557 | 613 | ||
| @@ -614,8 +670,8 @@ func TestPlayerRestartActivationFailureCleansUpResources(t *testing.T) { | |||
| 614 | 670 | // made — only SetStreamActive(true) is called. | |
| 615 | 671 | func TestPlayerRestartDoesNotStartSecondMainLoop(t *testing.T) { | |
| 616 | 672 | mockOps := mocks.NewMockStreamOps(t) | |
| 617 | - | fakeLoop := unsafe.Pointer(uintptr(0xCAFE)) | |
| 618 | - | fakeStream := unsafe.Pointer(uintptr(0xBEEF)) | |
| 673 | + | fakeLoop := opaqueTestPtr() | |
| 674 | + | fakeStream := opaqueTestPtr() | |
| 619 | 675 | cfg := defaultTestConfig() | |
| 620 | 676 | ||
| 621 | 677 | // 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 resources | 70 | // 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 destroy | 561 | // 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 called | 586 | // 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 + RunMainLoop | 677 | // First start: full resource creation + RunMainLoop | ||
M internal/loader/loader_test.go +12 -15
| @@ -1,7 +1,6 @@ | |||
| 1 | 1 | package loader | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "os" | |
| 5 | 4 | "testing" | |
| 6 | 5 | ) | |
| 7 | 6 | ||
| @@ -12,19 +11,17 @@ func TestLibFileNameLinuxDefault(t *testing.T) { | |||
| 12 | 11 | } | |
| 13 | 12 | ||
| 14 | 13 | 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 | + | }) | |
| 18 | 20 | ||
| 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 | + | }) | |
| 30 | 27 | } | |
| @@ -1,7 +1,6 @@ | |||||
| 1 | package loader | 1 | 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 env | 14 | + | 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 set | 21 | + | 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) { | |||
| 185 | 185 | callbacks := PlayerCallbacks{ | |
| 186 | 186 | Fill: func(buf *PCMBuffer) (int, error) { | |
| 187 | 187 | callCount++ | |
| 188 | - | if callCount == 1 { | |
| 188 | + | switch callCount { | |
| 189 | + | case 1: | |
| 189 | 190 | firstBuf = buf | |
| 190 | 191 | _ = firstBuf // Avoid unused variable warning - we'll use this for assertion later | |
| 191 | - | } else if callCount == 2 { | |
| 192 | + | case 2: | |
| 192 | 193 | // Verify the buffer is reusable - on second call it should be the same | |
| 193 | 194 | // pointer but with potentially updated contents | |
| 194 | 195 | 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 = buf | 190 | firstBuf = buf | ||
| 190 | _ = firstBuf // Avoid unused variable warning - we'll use this for assertion later | 191 | _ = 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 same | 193 | // Verify the buffer is reusable - on second call it should be the same | ||
| 193 | // pointer but with potentially updated contents | 194 | // pointer but with potentially updated contents | ||
| 194 | if buf == nil { | 195 | if buf == nil { | ||