1package wayland
2
3import (
4 "context"
5 "fmt"
6 "testing"
7
8 "github.com/bnema/neferwl/internal/logging"
9 "github.com/bnema/neferwl/internal/ports"
10)
11
12// TestEffectiveInputEmptyTreeAllocations guards against a heap-allocated
13// recursive closure for every input-region traversal of a tiled tree.
14func TestEffectiveInputEmptyTreeAllocations(t *testing.T) {
15 root := &surface{}
16 for range 64 {
17 child := &surface{}
18 root.sub.children = append(root.sub.children, child)
19 root.sub.layout = append(root.sub.layout, childLayout{child: child})
20 }
21 walk := func() {
22 all, rects := root.effectiveInput()
23 if all || len(rects) != 0 {
24 t.Fatal("empty tree has input")
25 }
26 }
27 if allocs := testing.AllocsPerRun(100, walk); allocs != 0 {
28 t.Errorf("empty tiled input traversal: %.1f allocs, want 0", allocs)
29 }
30}
31
32// TestTiledCommitPublishAllocations exercises real content publication without
33// a client connection, holding old snapshots while subsequent commits publish.
34func TestTiledCommitPublishAllocations(t *testing.T) {
35 // The top-level test is listed in Makefile perf-check; this subtest guards
36 // the per-commit update storage after its first retirement.
37 t.Run("queueUpdateSteadyState", func(t *testing.T) {
38 srv := &Server{fifoSurfaces: make(map[*surface]struct{}), frameReady: make(chan struct{}, 1)}
39 surf := &surface{server: srv}
40 commit := func() {
41 surf.queueUpdate()
42 surf.dropQueue()
43 }
44 commit()
45 if allocs := testing.AllocsPerRun(100, commit); allocs != 0 {
46 t.Errorf("steady-state queueUpdate/dropQueue: %.1f allocs/commit; want 0", allocs)
47 }
48 })
49 for _, count := range []int{16, 64} {
50 t.Run(fmt.Sprintf("%d", count), func(t *testing.T) {
51 channels := Channels{Contents: make(chan ports.SurfaceContent, 1)}
52 srv, err := New(Options{RuntimeDir: t.TempDir(), Outputs: testOutputs}, channels, logging.For(context.Background(), "wayland"))
53 if err != nil {
54 t.Fatal(err)
55 }
56 root := &surface{server: srv, content: ports.SurfaceContent{Width: 1, Height: 1}, has: true, identity: 1}
57 root.xdg = &xdgSurface{window: &window{id: 1, mapped: true}}
58 for i := range count {
59 child := &surface{server: srv, has: true, content: ports.SurfaceContent{Width: 1, Height: 1}, identity: uint64(i + 2)}
60 child.sub.parent = root
61 root.sub.children = append(root.sub.children, child)
62 root.sub.layout = append(root.sub.layout, childLayout{child: child, x: i})
63 }
64 publish := func() { root.redraw() }
65 root.treeDirty = true
66 publish()
67 first := srv.contents[1]
68 publish()
69 if first.Seq != 1 || len(first.Children) != count || len(first.DamageHistory) != 1 {
70 t.Fatalf("previous publication changed: %+v", first)
71 }
72 if allocs := testing.AllocsPerRun(100, publish); allocs > 2 {
73 t.Errorf("%d children: %.1f allocs/publication; want <=2", count, allocs)
74 } else {
75 t.Logf("%d children: %.1f allocs/unchanged publication", count, allocs)
76 }
77 // A queued commit with an unchanged layout must not allocate
78 // proportionally to the number of children.
79 root.sub.pendingLayout = root.sub.layout
80 queue := func() { _ = root.takePending() }
81 if allocs := testing.AllocsPerRun(100, queue); allocs > 0 {
82 t.Errorf("%d children: %.1f allocs/queued commit; want 0", count, allocs)
83 } else {
84 t.Logf("%d children: %.1f allocs/takePending", count, allocs)
85 }
86 commit := func() {
87 root.queueUpdate()
88 root.dropQueue()
89 }
90 if allocs := testing.AllocsPerRun(100, commit); allocs != 0 {
91 t.Errorf("%d children: %.1f allocs/queueUpdate; want 0", count, allocs)
92 } else {
93 t.Logf("%d children: %.1f allocs/queueUpdate", count, allocs)
94 }
95 if first.Seq != 1 || len(first.Children) != count || len(first.DamageHistory) != 1 || first.DamageHistory[0].Seq != 1 {
96 t.Fatal("previous publication was mutated")
97 }
98 // An applied child change rebuilds into a distinct immutable slice.
99 root.sub.children[count-1].version++
100 root.treeDirty = true
101 publish()
102 if first.Children[count-1].Version != 0 || srv.contents[1].Children[count-1].Version != 1 {
103 t.Fatal("child version leaked into an earlier publication")
104 }
105 if &first.Children[0] == &srv.contents[1].Children[0] {
106 t.Fatal("rebuild reused previously published children storage")
107 }
108 // Rebuilding a changed child's immutable snapshot must have a
109 // constant allocation count, regardless of tree size.
110 child := root.sub.children[count-1]
111 changed := func() {
112 child.version++
113 root.treeDirty = true
114 root.redraw()
115 }
116 if allocs := testing.AllocsPerRun(100, changed); allocs > 3 {
117 t.Errorf("%d children: %.1f allocs/changed publication; want <=3", count, allocs)
118 } else {
119 t.Logf("%d children: %.1f allocs/changed publication", count, allocs)
120 }
121 })
122 }
123}