fix: preserve PipeWire cleanup error details
- commit
- 70342dc0892ce54dcff87afa8b8db91960e95592
- author
- brice · · 2026-04-10 04:21 UTC
- parent
- c41bf8c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +244 -22
M internal/capi/stream_ops.go +24 -11
| @@ -1,7 +1,6 @@ | |||
| 1 | 1 | package capi | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "errors" | |
| 5 | 4 | "fmt" | |
| 6 | 5 | "sync" | |
| 7 | 6 | "unsafe" | |
| @@ -9,10 +8,20 @@ import ( | |||
| 9 | 8 | portout "github.com/bnema/purego-pipewire/internal/ports/out" | |
| 10 | 9 | ) | |
| 11 | 10 | ||
| 11 | + | // PWError represents a PipeWire C API error with the function name and return code. | |
| 12 | + | type PWError struct { | |
| 13 | + | Func string | |
| 14 | + | Code int32 | |
| 15 | + | } | |
| 16 | + | ||
| 17 | + | func (e *PWError) Error() string { | |
| 18 | + | return fmt.Sprintf("%s failed: %d", e.Func, e.Code) | |
| 19 | + | } | |
| 20 | + | ||
| 12 | 21 | var ( | |
| 13 | - | errStreamCreate = errors.New("pw_stream_new_simple returned nil") | |
| 14 | - | errStreamConnect = errors.New("pw_stream_connect failed") | |
| 15 | - | errMainLoopCreate = errors.New("pw_main_loop_new returned nil") | |
| 22 | + | errStreamCreate = &PWError{Func: "pw_stream_new_simple", Code: 0} | |
| 23 | + | errStreamConnect = &PWError{Func: "pw_stream_connect", Code: 0} | |
| 24 | + | errMainLoopCreate = &PWError{Func: "pw_main_loop_new", Code: 0} | |
| 16 | 25 | ) | |
| 17 | 26 | ||
| 18 | 27 | const pwVersionStreamEvents = 2 | |
| @@ -82,7 +91,7 @@ func (s *streamOpsImpl) ConnectPlaybackStream(streamPtr unsafe.Pointer) error { | |||
| 82 | 91 | // PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS = 0x01 | 0x04 | |
| 83 | 92 | ret := pw_stream_connect(streamPtr, pwDirectionOutput, pwIDAny, pwStreamFlagAutoConnectMapBuffers, nil, 0) | |
| 84 | 93 | if ret < 0 { | |
| 85 | - | return errStreamConnect | |
| 94 | + | return &PWError{Func: "pw_stream_connect", Code: ret} | |
| 86 | 95 | } | |
| 87 | 96 | return nil | |
| 88 | 97 | } | |
| @@ -90,7 +99,7 @@ func (s *streamOpsImpl) ConnectPlaybackStream(streamPtr unsafe.Pointer) error { | |||
| 90 | 99 | func (s *streamOpsImpl) SetStreamActive(streamPtr unsafe.Pointer, active bool) error { | |
| 91 | 100 | ret := pw_stream_set_active(streamPtr, active) | |
| 92 | 101 | if ret < 0 { | |
| 93 | - | return fmt.Errorf("pw_stream_set_active failed: %d", ret) | |
| 102 | + | return &PWError{Func: "pw_stream_set_active", Code: ret} | |
| 94 | 103 | } | |
| 95 | 104 | return nil | |
| 96 | 105 | } | |
| @@ -102,15 +111,19 @@ func (s *streamOpsImpl) DequeueBuffer(streamPtr unsafe.Pointer) unsafe.Pointer { | |||
| 102 | 111 | func (s *streamOpsImpl) QueueBuffer(streamPtr unsafe.Pointer, bufPtr unsafe.Pointer) error { | |
| 103 | 112 | ret := pw_stream_queue_buffer(streamPtr, bufPtr) | |
| 104 | 113 | if ret < 0 { | |
| 105 | - | return fmt.Errorf("pw_stream_queue_buffer failed: %d", ret) | |
| 114 | + | return &PWError{Func: "pw_stream_queue_buffer", Code: ret} | |
| 106 | 115 | } | |
| 107 | 116 | return nil | |
| 108 | 117 | } | |
| 109 | 118 | ||
| 110 | - | // DisconnectStream intentionally ignores the PipeWire return code. | |
| 111 | - | // Close-path cleanup is best-effort here, and destroy is the definitive release. | |
| 112 | - | func (s *streamOpsImpl) DisconnectStream(streamPtr unsafe.Pointer) { | |
| 113 | - | pw_stream_disconnect(streamPtr) | |
| 119 | + | // DisconnectStream disconnects the stream from its port. | |
| 120 | + | // Returns a PWError if the operation fails. | |
| 121 | + | func (s *streamOpsImpl) DisconnectStream(streamPtr unsafe.Pointer) error { | |
| 122 | + | ret := pw_stream_disconnect(streamPtr) | |
| 123 | + | if ret < 0 { | |
| 124 | + | return &PWError{Func: "pw_stream_disconnect", Code: ret} | |
| 125 | + | } | |
| 126 | + | return nil | |
| 114 | 127 | } | |
| 115 | 128 | ||
| 116 | 129 | func (s *streamOpsImpl) DestroyStream(streamPtr unsafe.Pointer) { | |
| @@ -1,7 +1,6 @@ | |||||
| 1 | package capi | 1 | package capi | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "errors" | |||
| 5 | "fmt" | 4 | "fmt" | ||
| 6 | "sync" | 5 | "sync" | ||
| 7 | "unsafe" | 6 | "unsafe" | ||
| @@ -9,10 +8,20 @@ import ( | |||||
| 9 | portout "github.com/bnema/purego-pipewire/internal/ports/out" | 8 | portout "github.com/bnema/purego-pipewire/internal/ports/out" | ||
| 10 | ) | 9 | ) | ||
| 11 | 10 | ||||
| 11 | + | // PWError represents a PipeWire C API error with the function name and return code. | |||
| 12 | + | type PWError struct { | |||
| 13 | + | Func string | |||
| 14 | + | Code int32 | |||
| 15 | + | } | |||
| 16 | + | ||||
| 17 | + | func (e *PWError) Error() string { | |||
| 18 | + | return fmt.Sprintf("%s failed: %d", e.Func, e.Code) | |||
| 19 | + | } | |||
| 20 | + | ||||
| 12 | var ( | 21 | var ( | ||
| 13 | - | errStreamCreate = errors.New("pw_stream_new_simple returned nil") | 22 | + | errStreamCreate = &PWError{Func: "pw_stream_new_simple", Code: 0} |
| 14 | - | errStreamConnect = errors.New("pw_stream_connect failed") | 23 | + | errStreamConnect = &PWError{Func: "pw_stream_connect", Code: 0} |
| 15 | - | errMainLoopCreate = errors.New("pw_main_loop_new returned nil") | 24 | + | errMainLoopCreate = &PWError{Func: "pw_main_loop_new", Code: 0} |
| 16 | ) | 25 | ) | ||
| 17 | 26 | ||||
| 18 | const pwVersionStreamEvents = 2 | 27 | const pwVersionStreamEvents = 2 | ||
| @@ -82,7 +91,7 @@ func (s *streamOpsImpl) ConnectPlaybackStream(streamPtr unsafe.Pointer) error { | |||||
| 82 | // PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS = 0x01 | 0x04 | 91 | // PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS = 0x01 | 0x04 | ||
| 83 | ret := pw_stream_connect(streamPtr, pwDirectionOutput, pwIDAny, pwStreamFlagAutoConnectMapBuffers, nil, 0) | 92 | ret := pw_stream_connect(streamPtr, pwDirectionOutput, pwIDAny, pwStreamFlagAutoConnectMapBuffers, nil, 0) | ||
| 84 | if ret < 0 { | 93 | if ret < 0 { | ||
| 85 | - | return errStreamConnect | 94 | + | return &PWError{Func: "pw_stream_connect", Code: ret} |
| 86 | } | 95 | } | ||
| 87 | return nil | 96 | return nil | ||
| 88 | } | 97 | } | ||
| @@ -90,7 +99,7 @@ func (s *streamOpsImpl) ConnectPlaybackStream(streamPtr unsafe.Pointer) error { | |||||
| 90 | func (s *streamOpsImpl) SetStreamActive(streamPtr unsafe.Pointer, active bool) error { | 99 | func (s *streamOpsImpl) SetStreamActive(streamPtr unsafe.Pointer, active bool) error { | ||
| 91 | ret := pw_stream_set_active(streamPtr, active) | 100 | ret := pw_stream_set_active(streamPtr, active) | ||
| 92 | if ret < 0 { | 101 | if ret < 0 { | ||
| 93 | - | return fmt.Errorf("pw_stream_set_active failed: %d", ret) | 102 | + | return &PWError{Func: "pw_stream_set_active", Code: ret} |
| 94 | } | 103 | } | ||
| 95 | return nil | 104 | return nil | ||
| 96 | } | 105 | } | ||
| @@ -102,15 +111,19 @@ func (s *streamOpsImpl) DequeueBuffer(streamPtr unsafe.Pointer) unsafe.Pointer { | |||||
| 102 | func (s *streamOpsImpl) QueueBuffer(streamPtr unsafe.Pointer, bufPtr unsafe.Pointer) error { | 111 | func (s *streamOpsImpl) QueueBuffer(streamPtr unsafe.Pointer, bufPtr unsafe.Pointer) error { | ||
| 103 | ret := pw_stream_queue_buffer(streamPtr, bufPtr) | 112 | ret := pw_stream_queue_buffer(streamPtr, bufPtr) | ||
| 104 | if ret < 0 { | 113 | if ret < 0 { | ||
| 105 | - | return fmt.Errorf("pw_stream_queue_buffer failed: %d", ret) | 114 | + | return &PWError{Func: "pw_stream_queue_buffer", Code: ret} |
| 106 | } | 115 | } | ||
| 107 | return nil | 116 | return nil | ||
| 108 | } | 117 | } | ||
| 109 | 118 | ||||
| 110 | - | // DisconnectStream intentionally ignores the PipeWire return code. | 119 | + | // DisconnectStream disconnects the stream from its port. |
| 111 | - | // Close-path cleanup is best-effort here, and destroy is the definitive release. | 120 | + | // Returns a PWError if the operation fails. |
| 112 | - | func (s *streamOpsImpl) DisconnectStream(streamPtr unsafe.Pointer) { | 121 | + | func (s *streamOpsImpl) DisconnectStream(streamPtr unsafe.Pointer) error { |
| 113 | - | pw_stream_disconnect(streamPtr) | 122 | + | ret := pw_stream_disconnect(streamPtr) |
| 123 | + | if ret < 0 { | |||
| 124 | + | return &PWError{Func: "pw_stream_disconnect", Code: ret} | |||
| 125 | + | } | |||
| 126 | + | return nil | |||
| 114 | } | 127 | } | ||
| 115 | 128 | ||||
| 116 | func (s *streamOpsImpl) DestroyStream(streamPtr unsafe.Pointer) { | 129 | func (s *streamOpsImpl) DestroyStream(streamPtr unsafe.Pointer) { | ||
M internal/capi/stream_ops_test.go +141 -0
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package capi | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "errors" | |
| 4 | 5 | "testing" | |
| 5 | 6 | "unsafe" | |
| 6 | 7 | ) | |
| @@ -118,3 +119,143 @@ func TestQuitMainLoopCallsPwMainLoopQuit(t *testing.T) { | |||
| 118 | 119 | t.Fatalf("pw_main_loop_quit received %v, want %v", receivedPtr, fakeLoop) | |
| 119 | 120 | } | |
| 120 | 121 | } | |
| 122 | + | ||
| 123 | + | // TestConnectPlaybackStreamReturnsTypedErrorOnNegativeReturn verifies that | |
| 124 | + | // ConnectPlaybackStream returns a PWError with function name and return code. | |
| 125 | + | func TestConnectPlaybackStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |
| 126 | + | origConnect := pw_stream_connect | |
| 127 | + | t.Cleanup(func() { pw_stream_connect = origConnect }) | |
| 128 | + | ||
| 129 | + | pw_stream_connect = func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32 { | |
| 130 | + | return -22 // EINVAL | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | ops := &streamOpsImpl{} | |
| 134 | + | fakePtr := unsafe.Pointer(uintptr(0x1234)) | |
| 135 | + | ||
| 136 | + | err := ops.ConnectPlaybackStream(fakePtr) | |
| 137 | + | if err == nil { | |
| 138 | + | t.Fatal("expected error for negative return code, got nil") | |
| 139 | + | } | |
| 140 | + | ||
| 141 | + | var pwErr *PWError | |
| 142 | + | if !errors.As(err, &pwErr) { | |
| 143 | + | t.Fatalf("expected *PWError, got %T", err) | |
| 144 | + | } | |
| 145 | + | if pwErr.Func != "pw_stream_connect" { | |
| 146 | + | t.Errorf("expected Func='pw_stream_connect', got '%s'", pwErr.Func) | |
| 147 | + | } | |
| 148 | + | if pwErr.Code != -22 { | |
| 149 | + | t.Errorf("expected Code=-22, got %d", pwErr.Code) | |
| 150 | + | } | |
| 151 | + | } | |
| 152 | + | ||
| 153 | + | // TestSetStreamActiveReturnsTypedErrorOnNegativeReturn verifies that | |
| 154 | + | // SetStreamActive returns a PWError with function name and return code. | |
| 155 | + | func TestSetStreamActiveReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |
| 156 | + | origSetActive := pw_stream_set_active | |
| 157 | + | t.Cleanup(func() { pw_stream_set_active = origSetActive }) | |
| 158 | + | ||
| 159 | + | pw_stream_set_active = func(stream unsafe.Pointer, active bool) int32 { | |
| 160 | + | return -9 // EBADF | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | ops := &streamOpsImpl{} | |
| 164 | + | fakePtr := unsafe.Pointer(uintptr(0x5678)) | |
| 165 | + | ||
| 166 | + | err := ops.SetStreamActive(fakePtr, true) | |
| 167 | + | if err == nil { | |
| 168 | + | t.Fatal("expected error for negative return code, got nil") | |
| 169 | + | } | |
| 170 | + | ||
| 171 | + | var pwErr *PWError | |
| 172 | + | if !errors.As(err, &pwErr) { | |
| 173 | + | t.Fatalf("expected *PWError, got %T", err) | |
| 174 | + | } | |
| 175 | + | if pwErr.Func != "pw_stream_set_active" { | |
| 176 | + | t.Errorf("expected Func='pw_stream_set_active', got '%s'", pwErr.Func) | |
| 177 | + | } | |
| 178 | + | if pwErr.Code != -9 { | |
| 179 | + | t.Errorf("expected Code=-9, got %d", pwErr.Code) | |
| 180 | + | } | |
| 181 | + | } | |
| 182 | + | ||
| 183 | + | // TestQueueBufferReturnsTypedErrorOnNegativeReturn verifies that | |
| 184 | + | // QueueBuffer returns a PWError with function name and return code. | |
| 185 | + | func TestQueueBufferReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |
| 186 | + | origQueue := pw_stream_queue_buffer | |
| 187 | + | t.Cleanup(func() { pw_stream_queue_buffer = origQueue }) | |
| 188 | + | ||
| 189 | + | pw_stream_queue_buffer = func(stream unsafe.Pointer, buffer unsafe.Pointer) int32 { | |
| 190 | + | return -5 // EIO | |
| 191 | + | } | |
| 192 | + | ||
| 193 | + | ops := &streamOpsImpl{} | |
| 194 | + | fakePtr := unsafe.Pointer(uintptr(0x9ABC)) | |
| 195 | + | fakeBuf := unsafe.Pointer(uintptr(0xDEF0)) | |
| 196 | + | ||
| 197 | + | err := ops.QueueBuffer(fakePtr, fakeBuf) | |
| 198 | + | if err == nil { | |
| 199 | + | t.Fatal("expected error for negative return code, got nil") | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | var pwErr *PWError | |
| 203 | + | if !errors.As(err, &pwErr) { | |
| 204 | + | t.Fatalf("expected *PWError, got %T", err) | |
| 205 | + | } | |
| 206 | + | if pwErr.Func != "pw_stream_queue_buffer" { | |
| 207 | + | t.Errorf("expected Func='pw_stream_queue_buffer', got '%s'", pwErr.Func) | |
| 208 | + | } | |
| 209 | + | if pwErr.Code != -5 { | |
| 210 | + | t.Errorf("expected Code=-5, got %d", pwErr.Code) | |
| 211 | + | } | |
| 212 | + | } | |
| 213 | + | ||
| 214 | + | // TestDisconnectStreamReturnsTypedErrorOnNegativeReturn verifies that | |
| 215 | + | // DisconnectStream returns a PWError with function name and return code. | |
| 216 | + | func TestDisconnectStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |
| 217 | + | origDisconnect := pw_stream_disconnect | |
| 218 | + | t.Cleanup(func() { pw_stream_disconnect = origDisconnect }) | |
| 219 | + | ||
| 220 | + | pw_stream_disconnect = func(stream unsafe.Pointer) int32 { | |
| 221 | + | return -19 // ENODEV | |
| 222 | + | } | |
| 223 | + | ||
| 224 | + | ops := &streamOpsImpl{} | |
| 225 | + | fakePtr := unsafe.Pointer(uintptr(0x1111)) | |
| 226 | + | ||
| 227 | + | err := ops.DisconnectStream(fakePtr) | |
| 228 | + | if err == nil { | |
| 229 | + | t.Fatal("expected error for negative return code, got nil") | |
| 230 | + | } | |
| 231 | + | ||
| 232 | + | var pwErr *PWError | |
| 233 | + | if !errors.As(err, &pwErr) { | |
| 234 | + | t.Fatalf("expected *PWError, got %T", err) | |
| 235 | + | } | |
| 236 | + | if pwErr.Func != "pw_stream_disconnect" { | |
| 237 | + | t.Errorf("expected Func='pw_stream_disconnect', got '%s'", pwErr.Func) | |
| 238 | + | } | |
| 239 | + | if pwErr.Code != -19 { | |
| 240 | + | t.Errorf("expected Code=-19, got %d", pwErr.Code) | |
| 241 | + | } | |
| 242 | + | } | |
| 243 | + | ||
| 244 | + | // TestDisconnectStreamReturnsNilOnSuccess verifies that DisconnectStream | |
| 245 | + | // returns nil when the return code is non-negative. | |
| 246 | + | func TestDisconnectStreamReturnsNilOnSuccess(t *testing.T) { | |
| 247 | + | origDisconnect := pw_stream_disconnect | |
| 248 | + | t.Cleanup(func() { pw_stream_disconnect = origDisconnect }) | |
| 249 | + | ||
| 250 | + | pw_stream_disconnect = func(stream unsafe.Pointer) int32 { | |
| 251 | + | return 0 // Success | |
| 252 | + | } | |
| 253 | + | ||
| 254 | + | ops := &streamOpsImpl{} | |
| 255 | + | fakePtr := unsafe.Pointer(uintptr(0x2222)) | |
| 256 | + | ||
| 257 | + | err := ops.DisconnectStream(fakePtr) | |
| 258 | + | if err != nil { | |
| 259 | + | t.Fatalf("expected nil on success, got %v", err) | |
| 260 | + | } | |
| 261 | + | } | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package capi | 1 | package capi | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "errors" | |||
| 4 | "testing" | 5 | "testing" | ||
| 5 | "unsafe" | 6 | "unsafe" | ||
| 6 | ) | 7 | ) | ||
| @@ -118,3 +119,143 @@ func TestQuitMainLoopCallsPwMainLoopQuit(t *testing.T) { | |||||
| 118 | t.Fatalf("pw_main_loop_quit received %v, want %v", receivedPtr, fakeLoop) | 119 | t.Fatalf("pw_main_loop_quit received %v, want %v", receivedPtr, fakeLoop) | ||
| 119 | } | 120 | } | ||
| 120 | } | 121 | } | ||
| 122 | + | ||||
| 123 | + | // TestConnectPlaybackStreamReturnsTypedErrorOnNegativeReturn verifies that | |||
| 124 | + | // ConnectPlaybackStream returns a PWError with function name and return code. | |||
| 125 | + | func TestConnectPlaybackStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |||
| 126 | + | origConnect := pw_stream_connect | |||
| 127 | + | t.Cleanup(func() { pw_stream_connect = origConnect }) | |||
| 128 | + | ||||
| 129 | + | pw_stream_connect = func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32 { | |||
| 130 | + | return -22 // EINVAL | |||
| 131 | + | } | |||
| 132 | + | ||||
| 133 | + | ops := &streamOpsImpl{} | |||
| 134 | + | fakePtr := unsafe.Pointer(uintptr(0x1234)) | |||
| 135 | + | ||||
| 136 | + | err := ops.ConnectPlaybackStream(fakePtr) | |||
| 137 | + | if err == nil { | |||
| 138 | + | t.Fatal("expected error for negative return code, got nil") | |||
| 139 | + | } | |||
| 140 | + | ||||
| 141 | + | var pwErr *PWError | |||
| 142 | + | if !errors.As(err, &pwErr) { | |||
| 143 | + | t.Fatalf("expected *PWError, got %T", err) | |||
| 144 | + | } | |||
| 145 | + | if pwErr.Func != "pw_stream_connect" { | |||
| 146 | + | t.Errorf("expected Func='pw_stream_connect', got '%s'", pwErr.Func) | |||
| 147 | + | } | |||
| 148 | + | if pwErr.Code != -22 { | |||
| 149 | + | t.Errorf("expected Code=-22, got %d", pwErr.Code) | |||
| 150 | + | } | |||
| 151 | + | } | |||
| 152 | + | ||||
| 153 | + | // TestSetStreamActiveReturnsTypedErrorOnNegativeReturn verifies that | |||
| 154 | + | // SetStreamActive returns a PWError with function name and return code. | |||
| 155 | + | func TestSetStreamActiveReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |||
| 156 | + | origSetActive := pw_stream_set_active | |||
| 157 | + | t.Cleanup(func() { pw_stream_set_active = origSetActive }) | |||
| 158 | + | ||||
| 159 | + | pw_stream_set_active = func(stream unsafe.Pointer, active bool) int32 { | |||
| 160 | + | return -9 // EBADF | |||
| 161 | + | } | |||
| 162 | + | ||||
| 163 | + | ops := &streamOpsImpl{} | |||
| 164 | + | fakePtr := unsafe.Pointer(uintptr(0x5678)) | |||
| 165 | + | ||||
| 166 | + | err := ops.SetStreamActive(fakePtr, true) | |||
| 167 | + | if err == nil { | |||
| 168 | + | t.Fatal("expected error for negative return code, got nil") | |||
| 169 | + | } | |||
| 170 | + | ||||
| 171 | + | var pwErr *PWError | |||
| 172 | + | if !errors.As(err, &pwErr) { | |||
| 173 | + | t.Fatalf("expected *PWError, got %T", err) | |||
| 174 | + | } | |||
| 175 | + | if pwErr.Func != "pw_stream_set_active" { | |||
| 176 | + | t.Errorf("expected Func='pw_stream_set_active', got '%s'", pwErr.Func) | |||
| 177 | + | } | |||
| 178 | + | if pwErr.Code != -9 { | |||
| 179 | + | t.Errorf("expected Code=-9, got %d", pwErr.Code) | |||
| 180 | + | } | |||
| 181 | + | } | |||
| 182 | + | ||||
| 183 | + | // TestQueueBufferReturnsTypedErrorOnNegativeReturn verifies that | |||
| 184 | + | // QueueBuffer returns a PWError with function name and return code. | |||
| 185 | + | func TestQueueBufferReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |||
| 186 | + | origQueue := pw_stream_queue_buffer | |||
| 187 | + | t.Cleanup(func() { pw_stream_queue_buffer = origQueue }) | |||
| 188 | + | ||||
| 189 | + | pw_stream_queue_buffer = func(stream unsafe.Pointer, buffer unsafe.Pointer) int32 { | |||
| 190 | + | return -5 // EIO | |||
| 191 | + | } | |||
| 192 | + | ||||
| 193 | + | ops := &streamOpsImpl{} | |||
| 194 | + | fakePtr := unsafe.Pointer(uintptr(0x9ABC)) | |||
| 195 | + | fakeBuf := unsafe.Pointer(uintptr(0xDEF0)) | |||
| 196 | + | ||||
| 197 | + | err := ops.QueueBuffer(fakePtr, fakeBuf) | |||
| 198 | + | if err == nil { | |||
| 199 | + | t.Fatal("expected error for negative return code, got nil") | |||
| 200 | + | } | |||
| 201 | + | ||||
| 202 | + | var pwErr *PWError | |||
| 203 | + | if !errors.As(err, &pwErr) { | |||
| 204 | + | t.Fatalf("expected *PWError, got %T", err) | |||
| 205 | + | } | |||
| 206 | + | if pwErr.Func != "pw_stream_queue_buffer" { | |||
| 207 | + | t.Errorf("expected Func='pw_stream_queue_buffer', got '%s'", pwErr.Func) | |||
| 208 | + | } | |||
| 209 | + | if pwErr.Code != -5 { | |||
| 210 | + | t.Errorf("expected Code=-5, got %d", pwErr.Code) | |||
| 211 | + | } | |||
| 212 | + | } | |||
| 213 | + | ||||
| 214 | + | // TestDisconnectStreamReturnsTypedErrorOnNegativeReturn verifies that | |||
| 215 | + | // DisconnectStream returns a PWError with function name and return code. | |||
| 216 | + | func TestDisconnectStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |||
| 217 | + | origDisconnect := pw_stream_disconnect | |||
| 218 | + | t.Cleanup(func() { pw_stream_disconnect = origDisconnect }) | |||
| 219 | + | ||||
| 220 | + | pw_stream_disconnect = func(stream unsafe.Pointer) int32 { | |||
| 221 | + | return -19 // ENODEV | |||
| 222 | + | } | |||
| 223 | + | ||||
| 224 | + | ops := &streamOpsImpl{} | |||
| 225 | + | fakePtr := unsafe.Pointer(uintptr(0x1111)) | |||
| 226 | + | ||||
| 227 | + | err := ops.DisconnectStream(fakePtr) | |||
| 228 | + | if err == nil { | |||
| 229 | + | t.Fatal("expected error for negative return code, got nil") | |||
| 230 | + | } | |||
| 231 | + | ||||
| 232 | + | var pwErr *PWError | |||
| 233 | + | if !errors.As(err, &pwErr) { | |||
| 234 | + | t.Fatalf("expected *PWError, got %T", err) | |||
| 235 | + | } | |||
| 236 | + | if pwErr.Func != "pw_stream_disconnect" { | |||
| 237 | + | t.Errorf("expected Func='pw_stream_disconnect', got '%s'", pwErr.Func) | |||
| 238 | + | } | |||
| 239 | + | if pwErr.Code != -19 { | |||
| 240 | + | t.Errorf("expected Code=-19, got %d", pwErr.Code) | |||
| 241 | + | } | |||
| 242 | + | } | |||
| 243 | + | ||||
| 244 | + | // TestDisconnectStreamReturnsNilOnSuccess verifies that DisconnectStream | |||
| 245 | + | // returns nil when the return code is non-negative. | |||
| 246 | + | func TestDisconnectStreamReturnsNilOnSuccess(t *testing.T) { | |||
| 247 | + | origDisconnect := pw_stream_disconnect | |||
| 248 | + | t.Cleanup(func() { pw_stream_disconnect = origDisconnect }) | |||
| 249 | + | ||||
| 250 | + | pw_stream_disconnect = func(stream unsafe.Pointer) int32 { | |||
| 251 | + | return 0 // Success | |||
| 252 | + | } | |||
| 253 | + | ||||
| 254 | + | ops := &streamOpsImpl{} | |||
| 255 | + | fakePtr := unsafe.Pointer(uintptr(0x2222)) | |||
| 256 | + | ||||
| 257 | + | err := ops.DisconnectStream(fakePtr) | |||
| 258 | + | if err != nil { | |||
| 259 | + | t.Fatalf("expected nil on success, got %v", err) | |||
| 260 | + | } | |||
| 261 | + | } | |||
M internal/core/player.go +4 -1
| @@ -220,6 +220,8 @@ func (p *player) deactivateStream() error { | |||
| 220 | 220 | ||
| 221 | 221 | // teardown releases player-owned stream and main-loop resources through | |
| 222 | 222 | // StreamOps. Safe to call when fields are nil or on repeated invocation. | |
| 223 | + | // DisconnectStream is called before DestroyStream as best-effort cleanup; | |
| 224 | + | // if disconnect fails, teardown continues (destroy is the definitive release). | |
| 223 | 225 | func (p *player) teardown() { | |
| 224 | 226 | p.mu.Lock() | |
| 225 | 227 | streamOps := p.streamOps | |
| @@ -233,8 +235,9 @@ func (p *player) teardown() { | |||
| 233 | 235 | return | |
| 234 | 236 | } | |
| 235 | 237 | ||
| 236 | - | // Destroy stream first (it depends on the loop). | |
| 238 | + | // Disconnect then destroy stream (disconnect is best-effort, destroy is definitive). | |
| 237 | 239 | if streamPtr != nil { | |
| 240 | + | _ = streamOps.DisconnectStream(streamPtr) // Best-effort cleanup | |
| 238 | 241 | streamOps.DestroyStream(streamPtr) | |
| 239 | 242 | } | |
| 240 | 243 | ||
| @@ -220,6 +220,8 @@ func (p *player) deactivateStream() error { | |||||
| 220 | 220 | ||||
| 221 | // teardown releases player-owned stream and main-loop resources through | 221 | // teardown releases player-owned stream and main-loop resources through | ||
| 222 | // StreamOps. Safe to call when fields are nil or on repeated invocation. | 222 | // StreamOps. Safe to call when fields are nil or on repeated invocation. | ||
| 223 | + | // DisconnectStream is called before DestroyStream as best-effort cleanup; | |||
| 224 | + | // if disconnect fails, teardown continues (destroy is the definitive release). | |||
| 223 | func (p *player) teardown() { | 225 | func (p *player) teardown() { | ||
| 224 | p.mu.Lock() | 226 | p.mu.Lock() | ||
| 225 | streamOps := p.streamOps | 227 | streamOps := p.streamOps | ||
| @@ -233,8 +235,9 @@ func (p *player) teardown() { | |||||
| 233 | return | 235 | return | ||
| 234 | } | 236 | } | ||
| 235 | 237 | ||||
| 236 | - | // Destroy stream first (it depends on the loop). | 238 | + | // Disconnect then destroy stream (disconnect is best-effort, destroy is definitive). |
| 237 | if streamPtr != nil { | 239 | if streamPtr != nil { | ||
| 240 | + | _ = streamOps.DisconnectStream(streamPtr) // Best-effort cleanup | |||
| 238 | streamOps.DestroyStream(streamPtr) | 241 | streamOps.DestroyStream(streamPtr) | ||
| 239 | } | 242 | } | ||
| 240 | 243 | ||||
M internal/core/player_test.go +55 -2
| @@ -247,14 +247,15 @@ func TestPlayerStopWithoutStreamOpsIsNoop(t *testing.T) { | |||
| 247 | 247 | } | |
| 248 | 248 | ||
| 249 | 249 | // TestPlayerCloseTeardownCallsStreamOps verifies that Close() calls | |
| 250 | - | // DestroyStream, QuitMainLoop, and DestroyMainLoop through StreamOps | |
| 250 | + | // DisconnectStream, DestroyStream, QuitMainLoop, and DestroyMainLoop through StreamOps | |
| 251 | 251 | // when the corresponding pointers are set. | |
| 252 | 252 | func TestPlayerCloseTeardownCallsStreamOps(t *testing.T) { | |
| 253 | 253 | mockOps := mocks.NewMockStreamOps(t) | |
| 254 | 254 | fakeStream := unsafe.Pointer(uintptr(0xAAAA)) | |
| 255 | 255 | fakeLoop := unsafe.Pointer(uintptr(0xBBBB)) | |
| 256 | 256 | ||
| 257 | - | // Expect teardown calls in order: DestroyStream, QuitMainLoop, DestroyMainLoop. | |
| 257 | + | // Expect teardown calls in order: DisconnectStream, DestroyStream, QuitMainLoop, DestroyMainLoop. | |
| 258 | + | mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil) | |
| 258 | 259 | mockOps.EXPECT().DestroyStream(fakeStream).Return() | |
| 259 | 260 | mockOps.EXPECT().QuitMainLoop(fakeLoop).Return() | |
| 260 | 261 | mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return() | |
| @@ -306,6 +307,7 @@ func TestPlayerRepeatedCloseIsSafe(t *testing.T) { | |||
| 306 | 307 | fakeLoop := unsafe.Pointer(uintptr(0xDDDD)) | |
| 307 | 308 | ||
| 308 | 309 | // Only expect one set of teardown calls. | |
| 310 | + | mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil) | |
| 309 | 311 | mockOps.EXPECT().DestroyStream(fakeStream).Return() | |
| 310 | 312 | mockOps.EXPECT().QuitMainLoop(fakeLoop).Return() | |
| 311 | 313 | mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return() | |
| @@ -332,6 +334,7 @@ func TestPlayerTeardownWithOnlyStreamPtr(t *testing.T) { | |||
| 332 | 334 | mockOps := mocks.NewMockStreamOps(t) | |
| 333 | 335 | fakeStream := unsafe.Pointer(uintptr(0xEEEE)) | |
| 334 | 336 | ||
| 337 | + | mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil) | |
| 335 | 338 | mockOps.EXPECT().DestroyStream(fakeStream).Return() | |
| 336 | 339 | ||
| 337 | 340 | p := newPlayer(PlayerConfig{}, PlayerCallbacks{}) | |
| @@ -368,3 +371,53 @@ func TestPlayerTeardownWithOnlyLoopPtr(t *testing.T) { | |||
| 368 | 371 | t.Error("expected loopPtr to be nil after teardown") | |
| 369 | 372 | } | |
| 370 | 373 | } | |
| 374 | + | ||
| 375 | + | // TestPlayerTeardownCallsDisconnectStream verifies that teardown calls | |
| 376 | + | // DisconnectStream before DestroyStream when a stream exists. | |
| 377 | + | func TestPlayerTeardownCallsDisconnectStream(t *testing.T) { | |
| 378 | + | mockOps := mocks.NewMockStreamOps(t) | |
| 379 | + | fakeStream := unsafe.Pointer(uintptr(0xAAAA)) | |
| 380 | + | fakeLoop := unsafe.Pointer(uintptr(0xBBBB)) | |
| 381 | + | ||
| 382 | + | // Expect disconnect before destroy | |
| 383 | + | mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil) | |
| 384 | + | mockOps.EXPECT().DestroyStream(fakeStream).Return() | |
| 385 | + | mockOps.EXPECT().QuitMainLoop(fakeLoop).Return() | |
| 386 | + | mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return() | |
| 387 | + | ||
| 388 | + | p := newPlayer(PlayerConfig{}, PlayerCallbacks{}) | |
| 389 | + | p.streamOps = mockOps | |
| 390 | + | p.streamPtr = fakeStream | |
| 391 | + | p.loopPtr = fakeLoop | |
| 392 | + | p.setState(PlayerStatePlaying) | |
| 393 | + | ||
| 394 | + | if err := p.Close(); err != nil { | |
| 395 | + | t.Fatalf("Close failed: %v", err) | |
| 396 | + | } | |
| 397 | + | } | |
| 398 | + | ||
| 399 | + | // TestPlayerTeardownContinuesOnDisconnectError verifies that teardown | |
| 400 | + | // continues cleanup even if DisconnectStream returns an error. | |
| 401 | + | func TestPlayerTeardownContinuesOnDisconnectError(t *testing.T) { | |
| 402 | + | mockOps := mocks.NewMockStreamOps(t) | |
| 403 | + | fakeStream := unsafe.Pointer(uintptr(0xCCCC)) | |
| 404 | + | fakeLoop := unsafe.Pointer(uintptr(0xDDDD)) | |
| 405 | + | disconnectErr := errors.New("disconnect failed") | |
| 406 | + | ||
| 407 | + | // Disconnect fails, but destroy should still be called | |
| 408 | + | mockOps.EXPECT().DisconnectStream(fakeStream).Return(disconnectErr) | |
| 409 | + | mockOps.EXPECT().DestroyStream(fakeStream).Return() | |
| 410 | + | mockOps.EXPECT().QuitMainLoop(fakeLoop).Return() | |
| 411 | + | mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return() | |
| 412 | + | ||
| 413 | + | p := newPlayer(PlayerConfig{}, PlayerCallbacks{}) | |
| 414 | + | p.streamOps = mockOps | |
| 415 | + | p.streamPtr = fakeStream | |
| 416 | + | p.loopPtr = fakeLoop | |
| 417 | + | p.setState(PlayerStatePlaying) | |
| 418 | + | ||
| 419 | + | // Close should succeed even though disconnect failed (best-effort cleanup) | |
| 420 | + | if err := p.Close(); err != nil { | |
| 421 | + | t.Fatalf("Close should succeed despite disconnect error: %v", err) | |
| 422 | + | } | |
| 423 | + | } | |
| @@ -247,14 +247,15 @@ func TestPlayerStopWithoutStreamOpsIsNoop(t *testing.T) { | |||||
| 247 | } | 247 | } | ||
| 248 | 248 | ||||
| 249 | // TestPlayerCloseTeardownCallsStreamOps verifies that Close() calls | 249 | // TestPlayerCloseTeardownCallsStreamOps verifies that Close() calls | ||
| 250 | - | // DestroyStream, QuitMainLoop, and DestroyMainLoop through StreamOps | 250 | + | // DisconnectStream, DestroyStream, QuitMainLoop, and DestroyMainLoop through StreamOps |
| 251 | // when the corresponding pointers are set. | 251 | // when the corresponding pointers are set. | ||
| 252 | func TestPlayerCloseTeardownCallsStreamOps(t *testing.T) { | 252 | func TestPlayerCloseTeardownCallsStreamOps(t *testing.T) { | ||
| 253 | mockOps := mocks.NewMockStreamOps(t) | 253 | mockOps := mocks.NewMockStreamOps(t) | ||
| 254 | fakeStream := unsafe.Pointer(uintptr(0xAAAA)) | 254 | fakeStream := unsafe.Pointer(uintptr(0xAAAA)) | ||
| 255 | fakeLoop := unsafe.Pointer(uintptr(0xBBBB)) | 255 | fakeLoop := unsafe.Pointer(uintptr(0xBBBB)) | ||
| 256 | 256 | ||||
| 257 | - | // Expect teardown calls in order: DestroyStream, QuitMainLoop, DestroyMainLoop. | 257 | + | // Expect teardown calls in order: DisconnectStream, DestroyStream, QuitMainLoop, DestroyMainLoop. |
| 258 | + | mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil) | |||
| 258 | mockOps.EXPECT().DestroyStream(fakeStream).Return() | 259 | mockOps.EXPECT().DestroyStream(fakeStream).Return() | ||
| 259 | mockOps.EXPECT().QuitMainLoop(fakeLoop).Return() | 260 | mockOps.EXPECT().QuitMainLoop(fakeLoop).Return() | ||
| 260 | mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return() | 261 | mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return() | ||
| @@ -306,6 +307,7 @@ func TestPlayerRepeatedCloseIsSafe(t *testing.T) { | |||||
| 306 | fakeLoop := unsafe.Pointer(uintptr(0xDDDD)) | 307 | fakeLoop := unsafe.Pointer(uintptr(0xDDDD)) | ||
| 307 | 308 | ||||
| 308 | // Only expect one set of teardown calls. | 309 | // Only expect one set of teardown calls. | ||
| 310 | + | mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil) | |||
| 309 | mockOps.EXPECT().DestroyStream(fakeStream).Return() | 311 | mockOps.EXPECT().DestroyStream(fakeStream).Return() | ||
| 310 | mockOps.EXPECT().QuitMainLoop(fakeLoop).Return() | 312 | mockOps.EXPECT().QuitMainLoop(fakeLoop).Return() | ||
| 311 | mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return() | 313 | mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return() | ||
| @@ -332,6 +334,7 @@ func TestPlayerTeardownWithOnlyStreamPtr(t *testing.T) { | |||||
| 332 | mockOps := mocks.NewMockStreamOps(t) | 334 | mockOps := mocks.NewMockStreamOps(t) | ||
| 333 | fakeStream := unsafe.Pointer(uintptr(0xEEEE)) | 335 | fakeStream := unsafe.Pointer(uintptr(0xEEEE)) | ||
| 334 | 336 | ||||
| 337 | + | mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil) | |||
| 335 | mockOps.EXPECT().DestroyStream(fakeStream).Return() | 338 | mockOps.EXPECT().DestroyStream(fakeStream).Return() | ||
| 336 | 339 | ||||
| 337 | p := newPlayer(PlayerConfig{}, PlayerCallbacks{}) | 340 | p := newPlayer(PlayerConfig{}, PlayerCallbacks{}) | ||
| @@ -368,3 +371,53 @@ func TestPlayerTeardownWithOnlyLoopPtr(t *testing.T) { | |||||
| 368 | t.Error("expected loopPtr to be nil after teardown") | 371 | t.Error("expected loopPtr to be nil after teardown") | ||
| 369 | } | 372 | } | ||
| 370 | } | 373 | } | ||
| 374 | + | ||||
| 375 | + | // TestPlayerTeardownCallsDisconnectStream verifies that teardown calls | |||
| 376 | + | // DisconnectStream before DestroyStream when a stream exists. | |||
| 377 | + | func TestPlayerTeardownCallsDisconnectStream(t *testing.T) { | |||
| 378 | + | mockOps := mocks.NewMockStreamOps(t) | |||
| 379 | + | fakeStream := unsafe.Pointer(uintptr(0xAAAA)) | |||
| 380 | + | fakeLoop := unsafe.Pointer(uintptr(0xBBBB)) | |||
| 381 | + | ||||
| 382 | + | // Expect disconnect before destroy | |||
| 383 | + | mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil) | |||
| 384 | + | mockOps.EXPECT().DestroyStream(fakeStream).Return() | |||
| 385 | + | mockOps.EXPECT().QuitMainLoop(fakeLoop).Return() | |||
| 386 | + | mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return() | |||
| 387 | + | ||||
| 388 | + | p := newPlayer(PlayerConfig{}, PlayerCallbacks{}) | |||
| 389 | + | p.streamOps = mockOps | |||
| 390 | + | p.streamPtr = fakeStream | |||
| 391 | + | p.loopPtr = fakeLoop | |||
| 392 | + | p.setState(PlayerStatePlaying) | |||
| 393 | + | ||||
| 394 | + | if err := p.Close(); err != nil { | |||
| 395 | + | t.Fatalf("Close failed: %v", err) | |||
| 396 | + | } | |||
| 397 | + | } | |||
| 398 | + | ||||
| 399 | + | // TestPlayerTeardownContinuesOnDisconnectError verifies that teardown | |||
| 400 | + | // continues cleanup even if DisconnectStream returns an error. | |||
| 401 | + | func TestPlayerTeardownContinuesOnDisconnectError(t *testing.T) { | |||
| 402 | + | mockOps := mocks.NewMockStreamOps(t) | |||
| 403 | + | fakeStream := unsafe.Pointer(uintptr(0xCCCC)) | |||
| 404 | + | fakeLoop := unsafe.Pointer(uintptr(0xDDDD)) | |||
| 405 | + | disconnectErr := errors.New("disconnect failed") | |||
| 406 | + | ||||
| 407 | + | // Disconnect fails, but destroy should still be called | |||
| 408 | + | mockOps.EXPECT().DisconnectStream(fakeStream).Return(disconnectErr) | |||
| 409 | + | mockOps.EXPECT().DestroyStream(fakeStream).Return() | |||
| 410 | + | mockOps.EXPECT().QuitMainLoop(fakeLoop).Return() | |||
| 411 | + | mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return() | |||
| 412 | + | ||||
| 413 | + | p := newPlayer(PlayerConfig{}, PlayerCallbacks{}) | |||
| 414 | + | p.streamOps = mockOps | |||
| 415 | + | p.streamPtr = fakeStream | |||
| 416 | + | p.loopPtr = fakeLoop | |||
| 417 | + | p.setState(PlayerStatePlaying) | |||
| 418 | + | ||||
| 419 | + | // Close should succeed even though disconnect failed (best-effort cleanup) | |||
| 420 | + | if err := p.Close(); err != nil { | |||
| 421 | + | t.Fatalf("Close should succeed despite disconnect error: %v", err) | |||
| 422 | + | } | |||
| 423 | + | } | |||
M internal/ports/out/mocks/mock_StreamOps.go +18 -7
| @@ -345,9 +345,20 @@ func (_c *MockStreamOps_DestroyStream_Call) RunAndReturn(run func(streamPtr unsa | |||
| 345 | 345 | } | |
| 346 | 346 | ||
| 347 | 347 | // DisconnectStream provides a mock function for the type MockStreamOps | |
| 348 | - | func (_mock *MockStreamOps) DisconnectStream(streamPtr unsafe.Pointer) { | |
| 349 | - | _mock.Called(streamPtr) | |
| 350 | - | return | |
| 348 | + | func (_mock *MockStreamOps) DisconnectStream(streamPtr unsafe.Pointer) error { | |
| 349 | + | ret := _mock.Called(streamPtr) | |
| 350 | + | ||
| 351 | + | if len(ret) == 0 { | |
| 352 | + | panic("no return value specified for DisconnectStream") | |
| 353 | + | } | |
| 354 | + | ||
| 355 | + | var r0 error | |
| 356 | + | if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) error); ok { | |
| 357 | + | r0 = returnFunc(streamPtr) | |
| 358 | + | } else { | |
| 359 | + | r0 = ret.Error(0) | |
| 360 | + | } | |
| 361 | + | return r0 | |
| 351 | 362 | } | |
| 352 | 363 | ||
| 353 | 364 | // MockStreamOps_DisconnectStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisconnectStream' | |
| @@ -374,13 +385,13 @@ func (_c *MockStreamOps_DisconnectStream_Call) Run(run func(streamPtr unsafe.Poi | |||
| 374 | 385 | return _c | |
| 375 | 386 | } | |
| 376 | 387 | ||
| 377 | - | func (_c *MockStreamOps_DisconnectStream_Call) Return() *MockStreamOps_DisconnectStream_Call { | |
| 378 | - | _c.Call.Return() | |
| 388 | + | func (_c *MockStreamOps_DisconnectStream_Call) Return(err error) *MockStreamOps_DisconnectStream_Call { | |
| 389 | + | _c.Call.Return(err) | |
| 379 | 390 | return _c | |
| 380 | 391 | } | |
| 381 | 392 | ||
| 382 | - | func (_c *MockStreamOps_DisconnectStream_Call) RunAndReturn(run func(streamPtr unsafe.Pointer)) *MockStreamOps_DisconnectStream_Call { | |
| 383 | - | _c.Run(run) | |
| 393 | + | func (_c *MockStreamOps_DisconnectStream_Call) RunAndReturn(run func(streamPtr unsafe.Pointer) error) *MockStreamOps_DisconnectStream_Call { | |
| 394 | + | _c.Call.Return(run) | |
| 384 | 395 | return _c | |
| 385 | 396 | } | |
| 386 | 397 | ||
| @@ -345,9 +345,20 @@ func (_c *MockStreamOps_DestroyStream_Call) RunAndReturn(run func(streamPtr unsa | |||||
| 345 | } | 345 | } | ||
| 346 | 346 | ||||
| 347 | // DisconnectStream provides a mock function for the type MockStreamOps | 347 | // DisconnectStream provides a mock function for the type MockStreamOps | ||
| 348 | - | func (_mock *MockStreamOps) DisconnectStream(streamPtr unsafe.Pointer) { | 348 | + | func (_mock *MockStreamOps) DisconnectStream(streamPtr unsafe.Pointer) error { |
| 349 | - | _mock.Called(streamPtr) | 349 | + | ret := _mock.Called(streamPtr) |
| 350 | - | return | 350 | + | |
| 351 | + | if len(ret) == 0 { | |||
| 352 | + | panic("no return value specified for DisconnectStream") | |||
| 353 | + | } | |||
| 354 | + | ||||
| 355 | + | var r0 error | |||
| 356 | + | if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) error); ok { | |||
| 357 | + | r0 = returnFunc(streamPtr) | |||
| 358 | + | } else { | |||
| 359 | + | r0 = ret.Error(0) | |||
| 360 | + | } | |||
| 361 | + | return r0 | |||
| 351 | } | 362 | } | ||
| 352 | 363 | ||||
| 353 | // MockStreamOps_DisconnectStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisconnectStream' | 364 | // MockStreamOps_DisconnectStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisconnectStream' | ||
| @@ -374,13 +385,13 @@ func (_c *MockStreamOps_DisconnectStream_Call) Run(run func(streamPtr unsafe.Poi | |||||
| 374 | return _c | 385 | return _c | ||
| 375 | } | 386 | } | ||
| 376 | 387 | ||||
| 377 | - | func (_c *MockStreamOps_DisconnectStream_Call) Return() *MockStreamOps_DisconnectStream_Call { | 388 | + | func (_c *MockStreamOps_DisconnectStream_Call) Return(err error) *MockStreamOps_DisconnectStream_Call { |
| 378 | - | _c.Call.Return() | 389 | + | _c.Call.Return(err) |
| 379 | return _c | 390 | return _c | ||
| 380 | } | 391 | } | ||
| 381 | 392 | ||||
| 382 | - | func (_c *MockStreamOps_DisconnectStream_Call) RunAndReturn(run func(streamPtr unsafe.Pointer)) *MockStreamOps_DisconnectStream_Call { | 393 | + | func (_c *MockStreamOps_DisconnectStream_Call) RunAndReturn(run func(streamPtr unsafe.Pointer) error) *MockStreamOps_DisconnectStream_Call { |
| 383 | - | _c.Run(run) | 394 | + | _c.Call.Return(run) |
| 384 | return _c | 395 | return _c | ||
| 385 | } | 396 | } | ||
| 386 | 397 | ||||
M internal/ports/out/stream_ops.go +2 -1
| @@ -25,7 +25,8 @@ type StreamOps interface { | |||
| 25 | 25 | QueueBuffer(streamPtr unsafe.Pointer, bufPtr unsafe.Pointer) error | |
| 26 | 26 | ||
| 27 | 27 | // DisconnectStream disconnects the stream. | |
| 28 | - | DisconnectStream(streamPtr unsafe.Pointer) | |
| 28 | + | // Returns an error if the operation fails. | |
| 29 | + | DisconnectStream(streamPtr unsafe.Pointer) error | |
| 29 | 30 | ||
| 30 | 31 | // DestroyStream destroys the stream and frees its resources. | |
| 31 | 32 | DestroyStream(streamPtr unsafe.Pointer) | |
| @@ -25,7 +25,8 @@ type StreamOps interface { | |||||
| 25 | QueueBuffer(streamPtr unsafe.Pointer, bufPtr unsafe.Pointer) error | 25 | QueueBuffer(streamPtr unsafe.Pointer, bufPtr unsafe.Pointer) error | ||
| 26 | 26 | ||||
| 27 | // DisconnectStream disconnects the stream. | 27 | // DisconnectStream disconnects the stream. | ||
| 28 | - | DisconnectStream(streamPtr unsafe.Pointer) | 28 | + | // Returns an error if the operation fails. |
| 29 | + | DisconnectStream(streamPtr unsafe.Pointer) error | |||
| 29 | 30 | ||||
| 30 | // DestroyStream destroys the stream and frees its resources. | 31 | // DestroyStream destroys the stream and frees its resources. | ||
| 31 | DestroyStream(streamPtr unsafe.Pointer) | 32 | DestroyStream(streamPtr unsafe.Pointer) | ||