1package wayland
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/neferwl/internal/ports"
8 "github.com/stretchr/testify/mock"
9)
10
11// The steady-state hot paths that run for every frame or commit allocate
12// nothing (or one pooled object per damage-history generation).
13
14func TestReleaseHeldAllocations(t *testing.T) {
15 out := &output{place: ports.OutputPlacement{Info: ports.OutputInfo{Name: "DP-2", RefreshMilli: 60000}}}
16 s := &Server{outputs: []*output{out}, reports: map[string]ports.OutputPresented{}}
17 reports := []ports.OutputPresented{{Output: "DP-2", Seen: map[ports.WindowID]uint64{1: 1}}}
18 now := time.Unix(100, 0)
19 s.releaseHeld(now, reports)
20 if allocs := testing.AllocsPerRun(100, func() { s.releaseHeld(now, reports) }); allocs != 0 {
21 t.Errorf("releaseHeld: %.1f allocs, want 0", allocs)
22 }
23}
24
25func TestCommitDamageAllocations(t *testing.T) {
26 srv := &Server{fifoSurfaces: map[*surface]struct{}{}}
27 surf := &surface{server: srv}
28 commit := func() {
29 surf.Damage(nil, 0, 0, 4, 4)
30 surf.DamageBuffer(nil, 1, 1, 2, 2)
31 surf.queueUpdate()
32 surf.queue[0].applyGraph()
33 }
34 commit()
35 if allocs := testing.AllocsPerRun(100, commit); allocs != 0 {
36 t.Errorf("damage requests + queueUpdate + apply: %.1f allocs, want 0", allocs)
37 }
38 // The queued update keeps its rects while the next commit collects its own.
39 surf.Damage(nil, 0, 0, 1, 1)
40 surf.queueUpdate()
41 first := surf.queue[0]
42 surf.Damage(nil, 5, 5, 2, 2)
43 if len(first.damage) != 1 || first.damage[0].W != 1 || len(surf.next.damage) != 1 || surf.next.damage[0].W != 2 {
44 t.Fatalf("damage shared between commits: queued %v pending %v", first.damage, surf.next.damage)
45 }
46 surf.queueUpdate()
47 if second := surf.queue[1]; len(second.damage) != 1 || second.damage[0].W != 2 || first.damage[0].W != 1 {
48 t.Fatalf("damage mixed up: %v %v", first.damage, second.damage)
49 }
50}
51
52// An explicit-sync commit moves its points into the update: the commit
53// itself allocates no commitSync, and a pending one is not overwritten.
54func TestCommitSyncStorageIsPerUpdate(t *testing.T) {
55 dev := newMocksyncobjDevice(t)
56 dev.EXPECT().signalled(mock.Anything, mock.Anything).Return(true, nil)
57 srv := &Server{fifoSurfaces: map[*surface]struct{}{}}
58 sw, err := newSyncWaiter(func() {})
59 if err != nil {
60 t.Fatal(err)
61 }
62 defer sw.pipeR.Close()
63 defer sw.close()
64 srv.syncWait = sw
65 tl := &timeline{dev: dev, handle: 7, alive: true}
66 surf := &surface{server: srv, sync: &syncState{}}
67 take := func(point uint64) {
68 surf.sync.acquire, surf.sync.release = syncPoint{tl: tl, point: point}, syncPoint{tl: tl, point: point + 1}
69 surf.takeSyncPoints()
70 surf.queueUpdate()
71 }
72 take(10)
73 take(20)
74 a, b := surf.queue[0], surf.queue[1]
75 if a.sync == nil || b.sync == nil || a.sync == b.sync || a.sync.acquire.point != 10 || b.sync.acquire.point != 20 || surf.next.sync != nil {
76 t.Fatalf("sync points mixed up: %+v %+v pending %+v", a.sync, b.sync, surf.next.sync)
77 }
78 // The commit's points live in the surface until queueUpdate: no heap
79 // commitSync per commit (the mocked device's own allocations would drown
80 // an AllocsPerRun count here).
81 surf.sync.acquire, surf.sync.release = syncPoint{tl: tl, point: 30}, syncPoint{tl: tl, point: 31}
82 surf.takeSyncPoints()
83 if surf.next.sync != &surf.syncScratch {
84 t.Fatal("commit points not held in the surface scratch")
85 }
86}
87
88func TestEmitContentAllocations(t *testing.T) {
89 s := &Server{
90 contentSeq: map[ports.WindowID]uint64{},
91 damage: map[ports.WindowID][]ports.SeqDamage{},
92 latest: map[ports.WindowID]ports.SurfaceContent{},
93 }
94 c := ports.SurfaceContent{ID: 1, Solid: &ports.SolidColor{}}
95 var held []ports.SurfaceContent // readers retaining published histories
96 var snapshots [][]ports.SeqDamage
97 for range 3 * damageHistoryCap {
98 s.emitContent(c, damage{full: true})
99 got := s.latest[1]
100 held = append(held, got)
101 snapshots = append(snapshots, append([]ports.SeqDamage(nil), got.DamageHistory...))
102 }
103 for i, h := range held {
104 // A published history never changes, nor can its owner append to it.
105 if len(h.DamageHistory) != len(snapshots[i]) || cap(h.DamageHistory) != len(h.DamageHistory) {
106 t.Fatalf("history %d: len %d cap %d", i, len(h.DamageHistory), cap(h.DamageHistory))
107 }
108 for j, d := range h.DamageHistory {
109 if d.Seq != snapshots[i][j].Seq || d.Full != snapshots[i][j].Full {
110 t.Fatalf("history %d changed at %d: %+v want %+v", i, j, d, snapshots[i][j])
111 }
112 }
113 if n := len(h.DamageHistory); h.DamageHistory[n-1].Seq != h.Seq || n > damageHistory {
114 t.Fatalf("history %d: %+v", i, h.DamageHistory)
115 }
116 }
117 allocs := testing.AllocsPerRun(100, func() { s.emitContent(c, damage{full: true}) })
118 if allocs != 0 {
119 t.Errorf("emitContent: %.2f allocs, want 0 (amortized)", allocs)
120 }
121 t.Logf("emitContent: %.2f allocs", allocs)
122}