1package app
2
3import (
4 "context"
5 "os"
6 "testing"
7
8 "github.com/bnema/neferwl/internal/ports"
9)
10
11func TestOutputRouting(t *testing.T) {
12 ctx, cancel := context.WithCancel(context.Background())
13 defer cancel()
14 replies := make(chan ports.CaptureDone, 2)
15 set := newOutputSet(ctx, replies)
16 for _, name := range []string{"A", "B"} {
17 set.start(ctx, name, func(ctx context.Context, _ <-chan ports.Scene, _ <-chan *ports.ContentBatch, _ ports.ContentReturn, _ <-chan ports.CursorChange, _ <-chan ports.CaptureRequest, _ <-chan ports.SecurityState, _ ports.OutputInstance) error {
18 <-ctx.Done()
19 return nil
20 })
21 <-set.outs[name].cursor // initial cursor
22 }
23 set.scenes([]ports.Scene{{Output: "A", Seq: 1}, {Output: "B", Seq: 2}})
24 set.scenes([]ports.Scene{{Output: "A", Seq: 3}})
25 if got := <-set.outs["A"].scenes; got.Seq != 3 {
26 t.Fatalf("A scene: %+v", got)
27 }
28 if got := <-set.outs["B"].scenes; got.Seq != 2 {
29 t.Fatalf("B scene: %+v", got)
30 }
31 set.setCursor(ports.CursorChange{Shape: "text"})
32 set.setCursor(ports.CursorChange{Shape: "pointer"})
33 for _, name := range []string{"A", "B"} {
34 if got := <-set.outs[name].cursor; got.Shape != "pointer" {
35 t.Fatalf("%s cursor: %+v", name, got)
36 }
37 }
38 f, err := os.CreateTemp(t.TempDir(), "capture")
39 if err != nil {
40 t.Fatal(err)
41 }
42 set.routeCapture(ports.CaptureRequest{ID: 7, Output: "B", Dst: ports.SHMBuffer{File: f}})
43 if got := <-set.outs["B"].captures; got.ID != 7 {
44 t.Fatalf("B capture: %+v", got)
45 }
46 if err := f.Close(); err != nil {
47 t.Fatal(err)
48 }
49 set.outs["A"].stop()
50 if err := set.finish("A"); err != nil {
51 t.Fatal(err)
52 }
53 set.scenes([]ports.Scene{{Output: "A", Seq: 4}})
54 if _, ok := set.outs["A"]; ok {
55 t.Fatal("removed output retained")
56 }
57 set.routeCapture(ports.CaptureRequest{ID: 8, Output: "A"})
58 if got := <-replies; got.ID != 8 || got.Err == nil {
59 t.Fatalf("removed output capture: %+v", got)
60 }
61 if err := set.wait(); err != nil {
62 t.Fatal(err)
63 }
64}
65
66// The set remembers which outputs scenes turned off, also once they are
67// gone: drm starts an output that reconnects off (Output.StartOff).
68func TestOutputSetRemembersOff(t *testing.T) {
69 set := newOutputSet(context.Background(), nil)
70 set.scenes([]ports.Scene{{Output: "A", Off: true}, {Output: "B"}})
71 if !set.off["A"] || set.off["B"] {
72 t.Fatal(set.off)
73 }
74 set.scenes([]ports.Scene{{Output: "A"}})
75 if len(set.off) != 0 {
76 t.Fatal(set.off)
77 }
78}
79
80func TestOutputRoutingAllocations(t *testing.T) {
81 set := newOutputSet(context.Background(), nil)
82 ctx, cancel := context.WithCancel(context.Background())
83 defer cancel()
84 set.start(ctx, "A", func(ctx context.Context, _ <-chan ports.Scene, _ <-chan *ports.ContentBatch, _ ports.ContentReturn, _ <-chan ports.CursorChange, _ <-chan ports.CaptureRequest, _ <-chan ports.SecurityState, _ ports.OutputInstance) error {
85 <-ctx.Done()
86 return nil
87 })
88 scenes := []ports.Scene{{Output: "A"}}
89 cursor := ports.CursorChange{Shape: "pointer"}
90 capture := ports.CaptureRequest{Output: "A"}
91 if got := testing.AllocsPerRun(100, func() {
92 set.scenes(scenes)
93 set.setCursor(cursor)
94 set.routeCapture(capture)
95 <-set.outs["A"].captures
96 }); got != 0 {
97 t.Fatalf("routing: %g allocations per run, want 0", got)
98 }
99 if err := set.wait(); err != nil {
100 t.Fatal(err)
101 }
102}