1package drm
2
3import (
4 "sync/atomic"
5 "testing"
6 "time"
7)
8
9// All transitions below use values only: no event reader, timer, clock or
10// KMS mock is needed to prove the serial and deadline rules.
11func TestFrameLifecycleStaleFlipAndRecovery(t *testing.T) {
12 var serials atomic.Uint64
13 l := frameLifecycle{serials: &serials}
14 start := time.Unix(100, 0)
15 old := l.userData(userFrame)
16 l.begin(pendingFrame{frame: true, queued: 7}, start)
17 l.endPending() // successful recovery modeset invalidates the old event
18 current := l.userData(userFrame)
19 l.begin(pendingFrame{frame: true, queued: 8}, start.Add(time.Millisecond))
20 if _, _, ok := l.flip(old); ok || !l.pending {
21 t.Fatal("old event completed new commit")
22 }
23 f, _, ok := l.flip(current)
24 if !ok || l.pending || !f.frame || f.queued != 8 {
25 t.Fatalf("new event: ok=%v pending=%v frame=%+v", ok, l.pending, f)
26 }
27}
28
29func TestFrameLifecycleBusyRetryAndLimit(t *testing.T) {
30 var serials atomic.Uint64
31 l := frameLifecycle{serials: &serials, stuckAfter: 200 * time.Millisecond}
32 start := time.Unix(100, 0)
33 if !l.busy(start) || l.stuckAt != start.Add(busyRetry) {
34 t.Fatal("initial EBUSY did not arm retry")
35 }
36 if got := l.timeout(start.Add(busyRetry-time.Nanosecond), true); got != timeoutEarly {
37 t.Fatalf("premature deadline: %d", got)
38 }
39 if got := l.timeout(start.Add(busyRetry), true); got != timeoutRetry || l.pending {
40 t.Fatalf("first retry: %d pending %v", got, l.pending)
41 }
42 l.busy(start.Add(busyRetry))
43 if _, _, ok := l.flip(999 << userKindBits); !ok || l.pending {
44 t.Fatal("CRTC event did not unblock EBUSY")
45 }
46 for i := 3; i < 6; i++ {
47 l.busy(start.Add(time.Duration(i) * busyRetry))
48 if got := l.timeout(start.Add(time.Duration(i+1)*busyRetry), true); got != timeoutRetry {
49 t.Fatalf("busy retry %d: %d", i, got)
50 }
51 }
52 l.busy(start.Add(6 * busyRetry))
53 if got := l.timeout(start.Add(7*busyRetry), true); got != timeoutBusy {
54 t.Fatalf("long EBUSY did not request modeset: %d", got)
55 }
56 if !l.busy(start.Add(time.Second)) {
57 t.Fatal("later refusal did not start a new busy run")
58 }
59 if got := l.timeout(start.Add(time.Second+busyRetry), true); got != timeoutRetry {
60 t.Fatalf("new busy run: %d", got)
61 }
62}
63
64func TestFrameLifecycleFenceAndMissingEvent(t *testing.T) {
65 var serials atomic.Uint64
66 l := frameLifecycle{serials: &serials, stuckAfter: 100 * time.Millisecond}
67 start := time.Unix(100, 0)
68 user := l.userData(userFrame)
69 l.begin(pendingFrame{frame: true}, start)
70 if got := l.timeout(start.Add(100*time.Millisecond), false); got != timeoutFence || !l.pending || l.stuckAt != start.Add(200*time.Millisecond) {
71 t.Fatalf("unsignalled fence: %d pending %v deadline %v", got, l.pending, l.stuckAt)
72 }
73 if got := l.timeout(start.Add(200*time.Millisecond), false); got != timeoutEarly || !l.pending {
74 t.Fatalf("repeated fence wait: %d", got)
75 }
76 if got := l.timeout(start.Add(300*time.Millisecond), true); got != timeoutMissing || l.pending {
77 t.Fatalf("missing event after fence: %d pending %v", got, l.pending)
78 }
79 if _, _, ok := l.flip(user); ok {
80 t.Fatal("late event completed abandoned commit")
81 }
82}
83
84func TestFrameLifecycleTransitionsAllocations(t *testing.T) {
85 var serials atomic.Uint64
86 l := frameLifecycle{serials: &serials}
87 start := time.Unix(100, 0)
88 if allocs := testing.AllocsPerRun(100, func() {
89 user := l.userData(userFrame)
90 l.begin(pendingFrame{frame: true}, start)
91 l.flip(user)
92 l.busy(start)
93 l.timeout(start.Add(busyRetry), true)
94 }); allocs != 0 {
95 t.Fatalf("frame lifecycle: %.1f allocs, want 0", allocs)
96 }
97}