1package drm
2
3import (
4 "context"
5 "errors"
6 "os"
7 "sync/atomic"
8 "testing"
9 "time"
10
11 portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
12 "github.com/bnema/neferwl/internal/ports"
13 "github.com/stretchr/testify/mock"
14 "golang.org/x/sys/unix"
15)
16
17func TestSecurityTransitionDuringDesktopRenderPreservesReadFence(t *testing.T) {
18 o, _, commits := testOutput(t)
19 o.cursor, o.scanout = nil, false
20 var state atomic.Uint64
21 securityGate(t, o, &state)
22 r := portsmocks.NewMockRenderer(t)
23 f, _ := protectionFence(t, false)
24 r.EXPECT().UseTarget(0).Return().Once()
25 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
26 state.Store(3)
27 return f, nil
28 }).Once()
29 _, err := o.submitFrame(context.Background(), r, ports.Scene{}, nil, nil, nil, nil)
30 if !errors.Is(err, errSecurityScene) || len(*commits) != 0 || o.readDone() {
31 t.Fatalf("old rendered frame committed or read fence dropped: %v", err)
32 }
33 o.dropRead()
34}
35
36func TestSecurityMasterLossInvalidatesWithoutProof(t *testing.T) {
37 o, _, _ := testOutput(t)
38 var state atomic.Uint64
39 state.Store(3)
40 securityGate(t, o, &state)
41 events := make(chan ports.SecurityBackendEvent, 2)
42 o.SecurityEvents = events
43 o.observeSecurity()
44 o.securityPrepared = true
45 enabled := true
46 if !o.commitFailed(unix.EACCES, &enabled) || enabled || o.securityPrepared {
47 t.Fatal("master loss retained proof")
48 }
49 if err := o.sendSecurityInvalid(context.Background()); err != nil {
50 t.Fatal(err)
51 }
52 ev := <-events
53 if invalid, ok := ev.(ports.SecurityOutputInvalidated); !ok || invalid.Generation != 1 || invalid.Instance != 77 {
54 t.Fatalf("invalid event %+v", ev)
55 }
56 if len(events) != 0 {
57 t.Fatal("master loss forged inactive proof")
58 }
59}
60
61func TestSecurityProofBackpressureRechecksEpoch(t *testing.T) {
62 o, k, _ := testOutput(t)
63 var state atomic.Uint64
64 state.Store(3)
65 securityGate(t, o, &state)
66 events := make(chan ports.SecurityBackendEvent) // no consumer until transition
67 o.SecurityEvents = events
68 r := portsmocks.NewMockRenderer(t)
69 cleared := make(chan struct{})
70 r.EXPECT().UseTarget(0).Return().Once()
71 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil).Once()
72 k.EXPECT().createBlob(mock.Anything).RunAndReturn(func([]byte) (uint32, error) { close(cleared); return 99, nil }).Once()
73 ctx, cancel := context.WithTimeout(context.Background(), time.Second)
74 defer cancel()
75 done := make(chan error, 1)
76 go func() { done <- o.prepareSecurity(ctx, r) }()
77 <-cleared
78 state.Store(5)
79 if err := <-done; err != nil {
80 t.Fatal(err)
81 }
82 select {
83 case ev := <-events:
84 t.Fatalf("obsolete proof emitted: %+v", ev)
85 default:
86 }
87 if o.securityPrepared {
88 t.Fatal("obsolete proof retained")
89 }
90}