1package drm
2
3import (
4 "context"
5 "os"
6 "sync/atomic"
7 "testing"
8 "time"
9
10 portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
11 "github.com/bnema/neferwl/internal/ports"
12 "github.com/stretchr/testify/mock"
13 "golang.org/x/sys/unix"
14)
15
16func TestSecurityUnlockedResumeUsesOwnerRetry(t *testing.T) {
17 for _, stage := range []string{"engage-during-request", "master-loss"} {
18 t.Run(stage, func(t *testing.T) {
19 var failures []error
20 if stage == "master-loss" {
21 failures = []error{unix.EACCES}
22 }
23 o, k, commits, mu := testOutputMu(t, failures...)
24 o.cursor, o.tearing = nil, false
25 var state atomic.Uint64
26 securityGate(t, o, &state)
27 events := make(chan ports.SecurityBackendEvent, 4)
28 o.SecurityEvents = events
29 active := make(chan bool, 2)
30 active <- false // retain existing images; first modeset is resume
31 wake := make(chan ports.SecurityState, 1)
32 o.SecurityChanges = wake
33 r := portsmocks.NewMockRenderer(t)
34 r.EXPECT().UseTarget(0).Return().Once()
35 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
36 if s.Seq != 0 || s.Background != "#000000" || len(s.Windows) != 0 {
37 t.Fatal("resume rendered desktop")
38 }
39 return nil, nil
40 }).Once()
41 r.EXPECT().Close().Return().Once()
42 var blobs atomic.Int32
43 k.EXPECT().createBlob(mock.Anything).RunAndReturn(func([]byte) (uint32, error) {
44 if blobs.Add(1) == 1 && stage == "engage-during-request" {
45 state.Store(3)
46 }
47 return 99, nil
48 })
49 k.EXPECT().destroyBlob(mock.Anything).Return(nil).Maybe()
50 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
51 defer cancel()
52 done := make(chan error, 1)
53 go func() {
54 done <- o.Run(ctx, func(int, int) (ports.Renderer, error) { return r, nil }, nil, active, nil, nil, nil, nil, nil, nil)
55 }()
56 active <- true
57 if stage == "master-loss" {
58 waitFor(t, func() bool { mu.Lock(); defer mu.Unlock(); return len(*commits) == 1 })
59 state.Store(3)
60 wake <- ports.SecurityState{}
61 select {
62 case ev := <-events:
63 if _, ok := ev.(ports.SecurityOutputProof); ok {
64 t.Fatal("master loss forged proof")
65 }
66 case <-time.After(20 * time.Millisecond):
67 }
68 select {
69 case err := <-done:
70 t.Fatalf("resume stopped owner: %v", err)
71 default:
72 }
73 active <- true // only real seat reacquisition permits preparation
74 }
75 for {
76 select {
77 case ev := <-events:
78 if p, ok := ev.(ports.SecurityOutputProof); ok {
79 if p.Proof.Generation != 1 || p.Proof.Instance != 77 {
80 t.Fatalf("proof %+v", p)
81 }
82 cancel()
83 if err := <-done; err != nil {
84 t.Fatal(err)
85 }
86 return
87 }
88 case err := <-done:
89 t.Fatalf("resume ended before fresh proof: %v", err)
90 case <-ctx.Done():
91 t.Fatal("missing fresh resume proof")
92 }
93 }
94 })
95 }
96}