detached d7dc935

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +33 -28
M integration/video_test.go +33 -28
@@ -13,7 +13,10 @@ import (
1313 "testing"
1414 "time"
1515
16+ "github.com/bnema/purego-pipewire/internal/ports/in/mocks"
1617 "github.com/bnema/purego-pipewire/pipewire"
18+ "github.com/bnema/purego-pipewire/spa"
19+ "github.com/stretchr/testify/mock"
1720 )
1821
1922 const (
@@ -21,9 +24,9 @@ const (
2124 vidH = 240
2225 )
2326
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 {
2730 state atomic.Int32
2831 nodeID atomic.Uint32
2932 format atomic.Uint32
@@ -34,37 +37,38 @@ type videoHandler struct {
3437 statesCh chan int32
3538 }
3639
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
5763 }
5864
59-func (h *videoHandler) BuffersChanged() { h.buffers.Add(1) }
60-
6165 type videoEnv struct {
6266 rt pipewire.Runtime
6367 loop pipewire.Loop
6468 ctx pipewire.Context
6569 core pipewire.Core
6670 src *pipewire.VideoSource
67- handler *videoHandler
71+ handler *videoEvents
6872 }
6973
7074 func (e *videoEnv) close() {
@@ -108,7 +112,8 @@ func newVideoEnv(t *testing.T) *videoEnv {
108112 if err != nil {
109113 t.Fatalf("Init: %v", err)
110114 }
111- e := &videoEnv{rt: rt, handler: newVideoHandler()}
115+ h, ev := newVideoHandler(t)
116+ e := &videoEnv{rt: rt, handler: ev}
112117 if e.loop, err = rt.NewLoop(); err != nil {
113118 e.close()
114119 t.Fatalf("NewLoop: %v", err)
@@ -124,7 +129,7 @@ func newVideoEnv(t *testing.T) *videoEnv {
124129 e.src, err = e.core.NewVideoSource(pipewire.VideoConfig{
125130 Name: "purego-pipewire-test", Width: vidW, Height: vidH, MaxFPS: 60,
126131 Formats: []pipewire.VideoFormat{pipewire.VideoFormatBGRx},
127- }, e.handler)
132+ }, h)
128133 if err != nil {
129134 e.close()
130135 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 = 24024 vidH = 240
22 )25 )
23 26
24-// videoHandler records events. It runs on the loop thread; the fields the test27+// 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.Int3230 state atomic.Int32
28 nodeID atomic.Uint3231 nodeID atomic.Uint32
29 format atomic.Uint3232 format atomic.Uint32
@@ -34,37 +37,38 @@ type videoHandler struct {
34 statesCh chan int3237 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.Runtime66 rt pipewire.Runtime
63 loop pipewire.Loop67 loop pipewire.Loop
64 ctx pipewire.Context68 ctx pipewire.Context
65 core pipewire.Core69 core pipewire.Core
66 src *pipewire.VideoSource70 src *pipewire.VideoSource
67- handler *videoHandler71+ 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)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close