test(integration): record video source events through the generated handler mock
- commit
- d7dc935e38467e87cdfef37bb33d76091e4065ad
- author
- brice · · 2026-10-15 02:40 UTC
- parent
- 85ac61e
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +33 -28
M integration/video_test.go +33 -28
| @@ -13,7 +13,10 @@ import ( | |||
| 13 | 13 | "testing" | |
| 14 | 14 | "time" | |
| 15 | 15 | ||
| 16 | + | "github.com/bnema/purego-pipewire/internal/ports/in/mocks" | |
| 16 | 17 | "github.com/bnema/purego-pipewire/pipewire" | |
| 18 | + | "github.com/bnema/purego-pipewire/spa" | |
| 19 | + | "github.com/stretchr/testify/mock" | |
| 17 | 20 | ) | |
| 18 | 21 | ||
| 19 | 22 | const ( | |
| @@ -21,9 +24,9 @@ const ( | |||
| 21 | 24 | vidH = 240 | |
| 22 | 25 | ) | |
| 23 | 26 | ||
| 24 | - | // videoHandler records events. It runs on the loop thread; the fields the test | |
| 25 | - | // goroutine reads are atomics, but the test runs on the loop thread as well. | |
| 26 | - | type videoHandler struct { | |
| 27 | + | // videoEvents records what the source reports. Callbacks run on the loop | |
| 28 | + | // thread; the fields the test reads are atomics. | |
| 29 | + | type videoEvents struct { | |
| 27 | 30 | state atomic.Int32 | |
| 28 | 31 | nodeID atomic.Uint32 | |
| 29 | 32 | format atomic.Uint32 | |
| @@ -34,37 +37,38 @@ type videoHandler struct { | |||
| 34 | 37 | statesCh chan int32 | |
| 35 | 38 | } | |
| 36 | 39 | ||
| 37 | - | func newVideoHandler() *videoHandler { | |
| 38 | - | return &videoHandler{statesCh: make(chan int32, 64)} | |
| 39 | - | } | |
| 40 | - | ||
| 41 | - | func (h *videoHandler) StateChanged(state int32, nodeID uint32, err string) { | |
| 42 | - | h.state.Store(state) | |
| 43 | - | if nodeID != 0 { | |
| 44 | - | h.nodeID.Store(nodeID) | |
| 45 | - | } | |
| 46 | - | select { | |
| 47 | - | case h.statesCh <- state: | |
| 48 | - | default: | |
| 49 | - | } | |
| 50 | - | } | |
| 51 | - | ||
| 52 | - | func (h *videoHandler) FormatChanged(f pipewire.VideoFormat, w, hh, stride int) { | |
| 53 | - | h.format.Store(uint32(f)) | |
| 54 | - | h.width.Store(int32(w)) | |
| 55 | - | h.height.Store(int32(hh)) | |
| 56 | - | h.stride.Store(int32(stride)) | |
| 40 | + | // newVideoHandler returns a generated VideoHandler mock that records every | |
| 41 | + | // callback into the returned events. | |
| 42 | + | func newVideoHandler(t *testing.T) (*mocks.MockVideoHandler, *videoEvents) { | |
| 43 | + | ev := &videoEvents{statesCh: make(chan int32, 64)} | |
| 44 | + | h := mocks.NewMockVideoHandler(t) | |
| 45 | + | h.EXPECT().StateChanged(mock.Anything, mock.Anything, mock.Anything).Run(func(state int32, nodeID uint32, _ string) { | |
| 46 | + | ev.state.Store(state) | |
| 47 | + | if nodeID != 0 { | |
| 48 | + | ev.nodeID.Store(nodeID) | |
| 49 | + | } | |
| 50 | + | select { | |
| 51 | + | case ev.statesCh <- state: | |
| 52 | + | default: | |
| 53 | + | } | |
| 54 | + | }).Maybe() | |
| 55 | + | h.EXPECT().FormatChanged(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(f spa.VideoFormat, w, hh, stride int) { | |
| 56 | + | ev.format.Store(uint32(f)) | |
| 57 | + | ev.width.Store(int32(w)) | |
| 58 | + | ev.height.Store(int32(hh)) | |
| 59 | + | ev.stride.Store(int32(stride)) | |
| 60 | + | }).Maybe() | |
| 61 | + | h.EXPECT().BuffersChanged().Run(func() { ev.buffers.Add(1) }).Maybe() | |
| 62 | + | return h, ev | |
| 57 | 63 | } | |
| 58 | 64 | ||
| 59 | - | func (h *videoHandler) BuffersChanged() { h.buffers.Add(1) } | |
| 60 | - | ||
| 61 | 65 | type videoEnv struct { | |
| 62 | 66 | rt pipewire.Runtime | |
| 63 | 67 | loop pipewire.Loop | |
| 64 | 68 | ctx pipewire.Context | |
| 65 | 69 | core pipewire.Core | |
| 66 | 70 | src *pipewire.VideoSource | |
| 67 | - | handler *videoHandler | |
| 71 | + | handler *videoEvents | |
| 68 | 72 | } | |
| 69 | 73 | ||
| 70 | 74 | func (e *videoEnv) close() { | |
| @@ -108,7 +112,8 @@ func newVideoEnv(t *testing.T) *videoEnv { | |||
| 108 | 112 | if err != nil { | |
| 109 | 113 | t.Fatalf("Init: %v", err) | |
| 110 | 114 | } | |
| 111 | - | e := &videoEnv{rt: rt, handler: newVideoHandler()} | |
| 115 | + | h, ev := newVideoHandler(t) | |
| 116 | + | e := &videoEnv{rt: rt, handler: ev} | |
| 112 | 117 | if e.loop, err = rt.NewLoop(); err != nil { | |
| 113 | 118 | e.close() | |
| 114 | 119 | t.Fatalf("NewLoop: %v", err) | |
| @@ -124,7 +129,7 @@ func newVideoEnv(t *testing.T) *videoEnv { | |||
| 124 | 129 | e.src, err = e.core.NewVideoSource(pipewire.VideoConfig{ | |
| 125 | 130 | Name: "purego-pipewire-test", Width: vidW, Height: vidH, MaxFPS: 60, | |
| 126 | 131 | Formats: []pipewire.VideoFormat{pipewire.VideoFormatBGRx}, | |
| 127 | - | }, e.handler) | |
| 132 | + | }, h) | |
| 128 | 133 | if err != nil { | |
| 129 | 134 | e.close() | |
| 130 | 135 | t.Fatalf("NewVideoSource: %v", err) | |
| @@ -13,7 +13,10 @@ import ( | |||||
| 13 | "testing" | 13 | "testing" | ||
| 14 | "time" | 14 | "time" | ||
| 15 | 15 | ||||
| 16 | + | "github.com/bnema/purego-pipewire/internal/ports/in/mocks" | |||
| 16 | "github.com/bnema/purego-pipewire/pipewire" | 17 | "github.com/bnema/purego-pipewire/pipewire" | ||
| 18 | + | "github.com/bnema/purego-pipewire/spa" | |||
| 19 | + | "github.com/stretchr/testify/mock" | |||
| 17 | ) | 20 | ) | ||
| 18 | 21 | ||||
| 19 | const ( | 22 | const ( | ||
| @@ -21,9 +24,9 @@ const ( | |||||
| 21 | vidH = 240 | 24 | vidH = 240 | ||
| 22 | ) | 25 | ) | ||
| 23 | 26 | ||||
| 24 | - | // videoHandler records events. It runs on the loop thread; the fields the test | 27 | + | // videoEvents records what the source reports. Callbacks run on the loop |
| 25 | - | // goroutine reads are atomics, but the test runs on the loop thread as well. | 28 | + | // thread; the fields the test reads are atomics. |
| 26 | - | type videoHandler struct { | 29 | + | type videoEvents struct { |
| 27 | state atomic.Int32 | 30 | state atomic.Int32 | ||
| 28 | nodeID atomic.Uint32 | 31 | nodeID atomic.Uint32 | ||
| 29 | format atomic.Uint32 | 32 | format atomic.Uint32 | ||
| @@ -34,37 +37,38 @@ type videoHandler struct { | |||||
| 34 | statesCh chan int32 | 37 | statesCh chan int32 | ||
| 35 | } | 38 | } | ||
| 36 | 39 | ||||
| 37 | - | func newVideoHandler() *videoHandler { | 40 | + | // newVideoHandler returns a generated VideoHandler mock that records every |
| 38 | - | return &videoHandler{statesCh: make(chan int32, 64)} | 41 | + | // callback into the returned events. |
| 39 | - | } | 42 | + | func newVideoHandler(t *testing.T) (*mocks.MockVideoHandler, *videoEvents) { |
| 40 | - | 43 | + | ev := &videoEvents{statesCh: make(chan int32, 64)} | |
| 41 | - | func (h *videoHandler) StateChanged(state int32, nodeID uint32, err string) { | 44 | + | h := mocks.NewMockVideoHandler(t) |
| 42 | - | h.state.Store(state) | 45 | + | h.EXPECT().StateChanged(mock.Anything, mock.Anything, mock.Anything).Run(func(state int32, nodeID uint32, _ string) { |
| 43 | - | if nodeID != 0 { | 46 | + | ev.state.Store(state) |
| 44 | - | h.nodeID.Store(nodeID) | 47 | + | if nodeID != 0 { |
| 45 | - | } | 48 | + | ev.nodeID.Store(nodeID) |
| 46 | - | select { | 49 | + | } |
| 47 | - | case h.statesCh <- state: | 50 | + | select { |
| 48 | - | default: | 51 | + | case ev.statesCh <- state: |
| 49 | - | } | 52 | + | default: |
| 50 | - | } | 53 | + | } |
| 51 | - | 54 | + | }).Maybe() | |
| 52 | - | func (h *videoHandler) FormatChanged(f pipewire.VideoFormat, w, hh, stride int) { | 55 | + | h.EXPECT().FormatChanged(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(f spa.VideoFormat, w, hh, stride int) { |
| 53 | - | h.format.Store(uint32(f)) | 56 | + | ev.format.Store(uint32(f)) |
| 54 | - | h.width.Store(int32(w)) | 57 | + | ev.width.Store(int32(w)) |
| 55 | - | h.height.Store(int32(hh)) | 58 | + | ev.height.Store(int32(hh)) |
| 56 | - | h.stride.Store(int32(stride)) | 59 | + | ev.stride.Store(int32(stride)) |
| 60 | + | }).Maybe() | |||
| 61 | + | h.EXPECT().BuffersChanged().Run(func() { ev.buffers.Add(1) }).Maybe() | |||
| 62 | + | return h, ev | |||
| 57 | } | 63 | } | ||
| 58 | 64 | ||||
| 59 | - | func (h *videoHandler) BuffersChanged() { h.buffers.Add(1) } | |||
| 60 | - | ||||
| 61 | type videoEnv struct { | 65 | type videoEnv struct { | ||
| 62 | rt pipewire.Runtime | 66 | rt pipewire.Runtime | ||
| 63 | loop pipewire.Loop | 67 | loop pipewire.Loop | ||
| 64 | ctx pipewire.Context | 68 | ctx pipewire.Context | ||
| 65 | core pipewire.Core | 69 | core pipewire.Core | ||
| 66 | src *pipewire.VideoSource | 70 | src *pipewire.VideoSource | ||
| 67 | - | handler *videoHandler | 71 | + | handler *videoEvents |
| 68 | } | 72 | } | ||
| 69 | 73 | ||||
| 70 | func (e *videoEnv) close() { | 74 | func (e *videoEnv) close() { | ||
| @@ -108,7 +112,8 @@ func newVideoEnv(t *testing.T) *videoEnv { | |||||
| 108 | if err != nil { | 112 | if err != nil { | ||
| 109 | t.Fatalf("Init: %v", err) | 113 | t.Fatalf("Init: %v", err) | ||
| 110 | } | 114 | } | ||
| 111 | - | e := &videoEnv{rt: rt, handler: newVideoHandler()} | 115 | + | h, ev := newVideoHandler(t) |
| 116 | + | e := &videoEnv{rt: rt, handler: ev} | |||
| 112 | if e.loop, err = rt.NewLoop(); err != nil { | 117 | if e.loop, err = rt.NewLoop(); err != nil { | ||
| 113 | e.close() | 118 | e.close() | ||
| 114 | t.Fatalf("NewLoop: %v", err) | 119 | t.Fatalf("NewLoop: %v", err) | ||
| @@ -124,7 +129,7 @@ func newVideoEnv(t *testing.T) *videoEnv { | |||||
| 124 | e.src, err = e.core.NewVideoSource(pipewire.VideoConfig{ | 129 | e.src, err = e.core.NewVideoSource(pipewire.VideoConfig{ | ||
| 125 | Name: "purego-pipewire-test", Width: vidW, Height: vidH, MaxFPS: 60, | 130 | Name: "purego-pipewire-test", Width: vidW, Height: vidH, MaxFPS: 60, | ||
| 126 | Formats: []pipewire.VideoFormat{pipewire.VideoFormatBGRx}, | 131 | Formats: []pipewire.VideoFormat{pipewire.VideoFormatBGRx}, | ||
| 127 | - | }, e.handler) | 132 | + | }, h) |
| 128 | if err != nil { | 133 | if err != nil { | ||
| 129 | e.close() | 134 | e.close() | ||
| 130 | t.Fatalf("NewVideoSource: %v", err) | 135 | t.Fatalf("NewVideoSource: %v", err) | ||