1package drm
2
3import (
4 "context"
5 "testing"
6 "time"
7
8 portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
9 "github.com/bnema/neferwl/internal/ports"
10 "github.com/stretchr/testify/mock"
11)
12
13func applyPipelines(o *Output) {
14 for _, p := range []*plane{o.primary, o.overlay} {
15 p.colorKnown, p.colorApplied = true, colorSDRToPQ
16 }
17}
18
19// Before the seat is disabled a plane that applies the pipeline is put back
20// on Bypass, in a blocking commit of the planes alone.
21func TestSeatDisableBypassesAppliedPipeline(t *testing.T) {
22 o, _, commits := colorOutput(t, true, nil)
23 applyPipelines(o)
24 o.bypassForSeatDisable()
25 if len(*commits) != 1 {
26 t.Fatalf("%d commits", len(*commits))
27 }
28 c := (*commits)[0]
29 if c.flags != 0 || c.user != 0 {
30 t.Fatalf("not a blocking commit: flags %#x user %d", c.flags, c.user)
31 }
32 for _, p := range []struct{ plane, prop uint32 }{{tPrimary, pColorPipe}, {tOverlay, pColorPipeOverlay}} {
33 if v, ok := c.req.value(p.plane, p.prop); !ok || v != 0 {
34 t.Errorf("plane %d COLOR_PIPELINE %d %v, want Bypass", p.plane, v, ok)
35 }
36 }
37 if len(c.req.objs) != 2 || o.pipelineApplied() {
38 t.Fatalf("objects %v, applied %v", c.req.objs, o.pipelineApplied())
39 }
40 // Done: a second request has nothing to do.
41 o.bypassForSeatDisable()
42 if len(*commits) != 1 {
43 t.Fatal("commit with Bypass applied")
44 }
45}
46
47// With nothing applied (or nothing known) no commit is made.
48func TestSeatDisableWithoutPipelineDoesNotCommit(t *testing.T) {
49 for name, setup := range map[string]func(*Output){
50 "bypass": func(o *Output) { o.colorBypassed() },
51 "unknown": func(o *Output) { o.forgetColor() },
52 "no pipeline plane": func(o *Output) {
53 o.primary.props = maps0(planeProps)
54 o.overlay.props = maps0(planeProps)
55 },
56 } {
57 t.Run(name, func(t *testing.T) {
58 o, _, commits := colorOutput(t, true, nil)
59 setup(o)
60 o.bypassForSeatDisable()
61 if len(*commits) != 0 {
62 t.Fatalf("%d commits", len(*commits))
63 }
64 })
65 }
66}
67
68// Run answers the request: the commit is made by the output's goroutine and
69// the caller is acked after it. The output stops drawing until the next enable.
70func TestRunPreparesSeatDisable(t *testing.T) {
71 o, k, commits, mu := testOutputMu(t)
72 o.cursor, o.tearing, o.fbs = nil, false, [2]uint32{}
73 props := maps0(planeProps)
74 props["COLOR_PIPELINE"] = pColorPipe
75 o.primary.props = props
76 o.primary.pipeline = &colorPipeline{}
77 o.primary.colorKnown, o.primary.colorApplied = true, colorSDRToPQ
78 r := portsmocks.NewMockRenderer(t)
79 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
80 r.EXPECT().Close().Return().Once()
81 k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe()
82 k.EXPECT().destroyBlob(mock.Anything).Return(nil).Maybe()
83 active := make(chan bool, 1)
84 active <- false // switched away: no modeset resets the state
85 ctx, cancel := context.WithCancel(context.Background())
86 done := make(chan error, 1)
87 go func() {
88 done <- o.Run(ctx, func(int, int) (ports.Renderer, error) { return r, nil }, nil, active, nil, nil, nil, make(chan ports.OutputPresented, 1), nil, nil)
89 }()
90 if !o.PrepareSeatDisable(5 * time.Second) {
91 t.Fatal("no ack")
92 }
93 mu.Lock()
94 n := len(*commits)
95 var v uint64
96 var ok bool
97 if n > 0 {
98 v, ok = (*commits)[0].req.value(tPrimary, pColorPipe)
99 }
100 mu.Unlock()
101 if n != 1 || !ok || v != 0 {
102 t.Fatalf("commits %d, COLOR_PIPELINE %d %v", n, v, ok)
103 }
104 cancel()
105 if err := <-done; err != nil {
106 t.Fatal(err)
107 }
108}
109
110// An output that is not running cannot answer: the caller is not held.
111func TestPrepareSeatDisableTimesOut(t *testing.T) {
112 o, _, _ := testOutput(t)
113 if o.PrepareSeatDisable(5 * time.Millisecond) {
114 t.Fatal("acked without a running output")
115 }
116}