test(app): stop the screencast motion injector when the app stops reading
- commit
- 8c80052bdd5634aa83984a88b06c88e9041dd365
- author
- brice · · 2026-10-15 02:28 UTC
- parent
- c1850b5
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +21 -7
M internal/app/screencast_test.go +21 -7
| @@ -11,6 +11,7 @@ import ( | |||
| 11 | 11 | "runtime" | |
| 12 | 12 | "strconv" | |
| 13 | 13 | "strings" | |
| 14 | + | "sync" | |
| 14 | 15 | "sync/atomic" | |
| 15 | 16 | "testing" | |
| 16 | 17 | "time" | |
| @@ -200,23 +201,36 @@ func TestScreencastStreamsMonitor(t *testing.T) { | |||
| 200 | 201 | ||
| 201 | 202 | // Pointer motion damages the output while the consumer runs. The events | |
| 202 | 203 | // are boxed once: sending them allocates nothing. | |
| 203 | - | var moving atomic.Bool | |
| 204 | - | moving.Store(true) | |
| 204 | + | stopMoving := make(chan struct{}) | |
| 205 | + | var stopOnce sync.Once | |
| 206 | + | stopMotion := func() { stopOnce.Do(func() { close(stopMoving) }) } | |
| 205 | 207 | a := ports.InputEvent(ports.SecurityInput{Event: ports.PointerMotion{X: 40, Y: 40}}) | |
| 206 | 208 | b := ports.InputEvent(ports.SecurityInput{Event: ports.PointerMotion{X: 200, Y: 160}}) | |
| 207 | 209 | moved := make(chan struct{}) | |
| 208 | 210 | go func() { | |
| 209 | 211 | defer close(moved) | |
| 210 | - | for i := 0; moving.Load(); i++ { | |
| 212 | + | tick := time.NewTicker(16 * time.Millisecond) | |
| 213 | + | defer tick.Stop() | |
| 214 | + | for i := 0; ; i++ { | |
| 211 | 215 | ev := a | |
| 212 | 216 | if i%2 == 1 { | |
| 213 | 217 | ev = b | |
| 214 | 218 | } | |
| 215 | - | app.input <- ev | |
| 216 | - | time.Sleep(16 * time.Millisecond) | |
| 219 | + | // Neither the send nor the wait may outlive the test: the app | |
| 220 | + | // may have stopped reading. | |
| 221 | + | select { | |
| 222 | + | case app.input <- ev: | |
| 223 | + | case <-stopMoving: | |
| 224 | + | return | |
| 225 | + | } | |
| 226 | + | select { | |
| 227 | + | case <-tick.C: | |
| 228 | + | case <-stopMoving: | |
| 229 | + | return | |
| 230 | + | } | |
| 217 | 231 | } | |
| 218 | 232 | }() | |
| 219 | - | defer func() { moving.Store(false); <-moved }() | |
| 233 | + | defer func() { stopMotion(); <-moved }() | |
| 220 | 234 | ||
| 221 | 235 | gst := exec.Command("gst-launch-1.0", "-v", "pipewiresrc", "path="+itoa(node), "stream-properties=props,media.type=Video", "num-buffers=300", "!", "fakesink", "silent=false") | |
| 222 | 236 | out, err := gst.StdoutPipe() | |
| @@ -283,7 +297,7 @@ func TestScreencastStreamsMonitor(t *testing.T) { | |||
| 283 | 297 | _ = gst.Process.Kill() | |
| 284 | 298 | t.Fatal("gst-launch-1.0 did not finish") | |
| 285 | 299 | } | |
| 286 | - | moving.Store(false) | |
| 300 | + | stopMotion() | |
| 287 | 301 | ||
| 288 | 302 | // A second client is another unique name: it is allowed (the list is | |
| 289 | 303 | // off) but the session is not its own. | |
| @@ -11,6 +11,7 @@ import ( | |||||
| 11 | "runtime" | 11 | "runtime" | ||
| 12 | "strconv" | 12 | "strconv" | ||
| 13 | "strings" | 13 | "strings" | ||
| 14 | + | "sync" | |||
| 14 | "sync/atomic" | 15 | "sync/atomic" | ||
| 15 | "testing" | 16 | "testing" | ||
| 16 | "time" | 17 | "time" | ||
| @@ -200,23 +201,36 @@ func TestScreencastStreamsMonitor(t *testing.T) { | |||||
| 200 | 201 | ||||
| 201 | // Pointer motion damages the output while the consumer runs. The events | 202 | // Pointer motion damages the output while the consumer runs. The events | ||
| 202 | // are boxed once: sending them allocates nothing. | 203 | // are boxed once: sending them allocates nothing. | ||
| 203 | - | var moving atomic.Bool | 204 | + | stopMoving := make(chan struct{}) |
| 204 | - | moving.Store(true) | 205 | + | var stopOnce sync.Once |
| 206 | + | stopMotion := func() { stopOnce.Do(func() { close(stopMoving) }) } | |||
| 205 | a := ports.InputEvent(ports.SecurityInput{Event: ports.PointerMotion{X: 40, Y: 40}}) | 207 | a := ports.InputEvent(ports.SecurityInput{Event: ports.PointerMotion{X: 40, Y: 40}}) | ||
| 206 | b := ports.InputEvent(ports.SecurityInput{Event: ports.PointerMotion{X: 200, Y: 160}}) | 208 | b := ports.InputEvent(ports.SecurityInput{Event: ports.PointerMotion{X: 200, Y: 160}}) | ||
| 207 | moved := make(chan struct{}) | 209 | moved := make(chan struct{}) | ||
| 208 | go func() { | 210 | go func() { | ||
| 209 | defer close(moved) | 211 | defer close(moved) | ||
| 210 | - | for i := 0; moving.Load(); i++ { | 212 | + | tick := time.NewTicker(16 * time.Millisecond) |
| 213 | + | defer tick.Stop() | |||
| 214 | + | for i := 0; ; i++ { | |||
| 211 | ev := a | 215 | ev := a | ||
| 212 | if i%2 == 1 { | 216 | if i%2 == 1 { | ||
| 213 | ev = b | 217 | ev = b | ||
| 214 | } | 218 | } | ||
| 215 | - | app.input <- ev | 219 | + | // Neither the send nor the wait may outlive the test: the app |
| 216 | - | time.Sleep(16 * time.Millisecond) | 220 | + | // may have stopped reading. |
| 221 | + | select { | |||
| 222 | + | case app.input <- ev: | |||
| 223 | + | case <-stopMoving: | |||
| 224 | + | return | |||
| 225 | + | } | |||
| 226 | + | select { | |||
| 227 | + | case <-tick.C: | |||
| 228 | + | case <-stopMoving: | |||
| 229 | + | return | |||
| 230 | + | } | |||
| 217 | } | 231 | } | ||
| 218 | }() | 232 | }() | ||
| 219 | - | defer func() { moving.Store(false); <-moved }() | 233 | + | defer func() { stopMotion(); <-moved }() |
| 220 | 234 | ||||
| 221 | gst := exec.Command("gst-launch-1.0", "-v", "pipewiresrc", "path="+itoa(node), "stream-properties=props,media.type=Video", "num-buffers=300", "!", "fakesink", "silent=false") | 235 | gst := exec.Command("gst-launch-1.0", "-v", "pipewiresrc", "path="+itoa(node), "stream-properties=props,media.type=Video", "num-buffers=300", "!", "fakesink", "silent=false") | ||
| 222 | out, err := gst.StdoutPipe() | 236 | out, err := gst.StdoutPipe() | ||
| @@ -283,7 +297,7 @@ func TestScreencastStreamsMonitor(t *testing.T) { | |||||
| 283 | _ = gst.Process.Kill() | 297 | _ = gst.Process.Kill() | ||
| 284 | t.Fatal("gst-launch-1.0 did not finish") | 298 | t.Fatal("gst-launch-1.0 did not finish") | ||
| 285 | } | 299 | } | ||
| 286 | - | moving.Store(false) | 300 | + | stopMotion() |
| 287 | 301 | ||||
| 288 | // A second client is another unique name: it is allowed (the list is | 302 | // A second client is another unique name: it is allowed (the list is | ||
| 289 | // off) but the session is not its own. | 303 | // off) but the session is not its own. | ||