1package browser
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/dumber/pkg/webkit"
8)
9
10func BenchmarkSplitNode(b *testing.B) {
11 wm := newTestWorkspaceManagerWithMocks(b)
12
13 b.ResetTimer()
14 for i := 0; i < b.N; i++ {
15 b.StopTimer()
16 // Reset to single node for each iteration
17 wm = newTestWorkspaceManagerWithMocks(b)
18 b.StartTimer()
19
20 _, err := wm.splitNode(wm.root, "right")
21 if err != nil {
22 b.Fatalf("Split failed: %v", err)
23 }
24 }
25}
26
27func BenchmarkClosePane(b *testing.B) {
28 for i := 0; i < b.N; i++ {
29 b.StopTimer()
30 wm := newTestWorkspaceManagerWithMocks(b)
31 node, err := wm.splitNode(wm.root, "right")
32 if err != nil {
33 b.Fatalf("Setup split failed: %v", err)
34 }
35 b.StartTimer()
36
37 err = wm.closePane(node)
38 if err != nil {
39 b.Fatalf("Close failed: %v", err)
40 }
41 }
42}
43
44func BenchmarkTreeTraversal(b *testing.B) {
45 wm := newTestWorkspaceManagerWithMocks(b)
46
47 // Build a complex tree with 15 leaves
48 leaves := []*paneNode{wm.root}
49 for i := 0; i < 14; i++ {
50 if len(leaves) > 0 {
51 // Split the first leaf
52 target := leaves[0]
53 leaves = leaves[1:] // Remove the target from leaves
54
55 newNode, err := wm.splitNode(target, "right")
56 if err != nil {
57 b.Fatalf("Setup split %d failed: %v", i, err)
58 }
59
60 // Add both resulting leaves back
61 leaves = append(leaves, target, newNode)
62 }
63 }
64
65 b.ResetTimer()
66 for i := 0; i < b.N; i++ {
67 wm.collectLeaves()
68 }
69}
70
71func BenchmarkCollectLeavesFrom(b *testing.B) {
72 wm := newTestWorkspaceManagerWithMocks(b)
73
74 // Build a balanced binary tree
75 for i := 0; i < 10; i++ {
76 leaves := wm.collectLeaves()
77 if len(leaves) > 0 {
78 _, err := wm.splitNode(leaves[0], "right")
79 if err != nil {
80 b.Fatalf("Setup split %d failed: %v", i, err)
81 }
82 }
83 }
84
85 b.ResetTimer()
86 for i := 0; i < b.N; i++ {
87 wm.collectLeavesFrom(wm.root)
88 }
89}
90
91func BenchmarkLeftmostLeaf(b *testing.B) {
92 wm := newTestWorkspaceManagerWithMocks(b)
93
94 // Create a deep left-leaning tree
95 current := wm.root
96 for i := 0; i < 20; i++ {
97 newNode, err := wm.splitNode(current, "left")
98 if err != nil {
99 b.Fatalf("Setup split %d failed: %v", i, err)
100 }
101 current = newNode
102 }
103
104 b.ResetTimer()
105 for i := 0; i < b.N; i++ {
106 wm.leftmostLeaf(wm.root)
107 }
108}
109
110func BenchmarkFindReplacementRoot(b *testing.B) {
111 wm := newTestWorkspaceManagerWithMocks(b)
112
113 // Build a complex tree
114 leaves := wm.collectLeaves()
115 for i := 0; i < 15; i++ {
116 if len(leaves) > 0 {
117 target := leaves[i%len(leaves)]
118 newNode, err := wm.splitNode(target, "right")
119 if err != nil {
120 continue
121 }
122 leaves = append(leaves, newNode)
123 }
124 }
125
126 excludeNode := wm.root
127
128 b.ResetTimer()
129 for i := 0; i < b.N; i++ {
130 wm.findReplacementRoot(excludeNode)
131 }
132}
133
134func BenchmarkFocusAdjacent(b *testing.B) {
135 wm := newTestWorkspaceManagerWithMocks(b)
136
137 // Create a 2x2 grid of panes with known geometry
138 right, _ := wm.splitNode(wm.root, "right")
139 bottom, _ := wm.splitNode(wm.root, "down")
140 _, _ = wm.splitNode(right, "down")
141
142 // Set up widget bounds for focus calculations
143 webkit.SetWidgetBoundsForTesting(wm.root.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100})
144 webkit.SetWidgetBoundsForTesting(right.container, webkit.WidgetBounds{X: 120, Y: 0, Width: 100, Height: 100})
145 webkit.SetWidgetBoundsForTesting(bottom.container, webkit.WidgetBounds{X: 0, Y: 120, Width: 100, Height: 100})
146
147 wm.active = wm.root
148
149 b.ResetTimer()
150 for i := 0; i < b.N; i++ {
151 wm.focusAdjacent("right")
152 wm.focusAdjacent("left")
153 }
154}
155
156func BenchmarkStructuralNeighbor(b *testing.B) {
157 wm := newTestWorkspaceManagerWithMocks(b)
158
159 // Create a complex nested structure
160 right, _ := wm.splitNode(wm.root, "right")
161 _, _ = wm.splitNode(right, "down")
162 _, _ = wm.splitNode(wm.root, "down")
163
164 // Set up geometry
165 webkit.SetWidgetBoundsForTesting(wm.root.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100})
166
167 b.ResetTimer()
168 for i := 0; i < b.N; i++ {
169 wm.structuralNeighbor(wm.root, "right")
170 wm.structuralNeighbor(wm.root, "down")
171 }
172}
173
174func BenchmarkClosestLeafFromSubtree(b *testing.B) {
175 wm := newTestWorkspaceManagerWithMocks(b)
176
177 // Build a subtree with multiple leaves
178 right, _ := wm.splitNode(wm.root, "right")
179 _, _ = wm.splitNode(right, "down")
180 _, _ = wm.splitNode(right, "up")
181
182 // Set up geometry for all nodes
183 leaves := wm.collectLeavesFrom(right)
184 for i, leaf := range leaves {
185 webkit.SetWidgetBoundsForTesting(leaf.container, webkit.WidgetBounds{
186 X: float64(i * 50), Y: 0, Width: 45, Height: 100,
187 })
188 }
189
190 b.ResetTimer()
191 for i := 0; i < b.N; i++ {
192 wm.closestLeafFromSubtree(right, 25, 50, "right")
193 }
194}
195
196func BenchmarkComplexTreeOperations(b *testing.B) {
197 b.Run("SplitAndClose", func(b *testing.B) {
198 for i := 0; i < b.N; i++ {
199 b.StopTimer()
200 wm := newTestWorkspaceManagerWithMocks(b)
201 b.StartTimer()
202
203 // Split multiple times then close in reverse order
204 nodes := []*paneNode{wm.root}
205 for j := 0; j < 5; j++ {
206 newNode, err := wm.splitNode(nodes[len(nodes)-1], "right")
207 if err != nil {
208 b.Fatalf("Split failed: %v", err)
209 }
210 nodes = append(nodes, newNode)
211 }
212
213 // Close all but root
214 for j := len(nodes) - 1; j > 0; j-- {
215 err := wm.closePane(nodes[j])
216 if err != nil {
217 b.Fatalf("Close failed: %v", err)
218 }
219 }
220 }
221 })
222
223 b.Run("BuildBalancedTree", func(b *testing.B) {
224 for i := 0; i < b.N; i++ {
225 b.StopTimer()
226 wm := newTestWorkspaceManagerWithMocks(b)
227 b.StartTimer()
228
229 // Build a balanced tree by alternating split directions
230 leaves := []*paneNode{wm.root}
231 directions := []string{"right", "down", "left", "up"}
232
233 for j := 0; j < 15 && len(leaves) > 0; j++ {
234 target := leaves[j%len(leaves)]
235 direction := directions[j%len(directions)]
236
237 newNode, err := wm.splitNode(target, direction)
238 if err != nil {
239 continue
240 }
241 leaves = append(leaves, newNode)
242 }
243 }
244 })
245}
246
247func BenchmarkMemoryOperations(b *testing.B) {
248 b.Run("ViewToNodeMapping", func(b *testing.B) {
249 wm := newTestWorkspaceManagerWithMocks(b)
250
251 // Create many nodes to stress the map
252 for i := 0; i < 100; i++ {
253 leaves := wm.collectLeaves()
254 if len(leaves) > 0 {
255 _, _ = wm.splitNode(leaves[0], "right")
256 }
257 }
258
259 b.ResetTimer()
260 for i := 0; i < b.N; i++ {
261 // Access all mappings
262 for view, node := range wm.viewToNode {
263 _ = view
264 _ = node
265 }
266 }
267 })
268
269 b.Run("TreeWalkMemory", func(b *testing.B) {
270 wm := newTestWorkspaceManagerWithMocks(b)
271
272 // Build deep tree
273 current := wm.root
274 for i := 0; i < 50; i++ {
275 newNode, err := wm.splitNode(current, "right")
276 if err != nil {
277 break
278 }
279 current = newNode
280 }
281
282 b.ResetTimer()
283 for i := 0; i < b.N; i++ {
284 // Multiple tree operations that allocate
285 leaves := wm.collectLeaves()
286 _ = leaves
287 countNodes(wm.root)
288 wm.leftmostLeaf(wm.root)
289 }
290 })
291}
292
293// Helper function for benchmarks
294func newTestWorkspaceManagerWithMocks(tb testing.TB) *WorkspaceManager {
295 tb.Helper()
296 webkit.ResetWidgetStubsForTesting()
297
298 rootView, err := webkit.NewWebView(&webkit.Config{})
299 if err != nil {
300 tb.Fatalf("failed to create root webview: %v", err)
301 }
302
303 rootPane := &BrowserPane{
304 webView: rootView,
305 id: "root",
306 }
307
308 app := &BrowserApp{
309 panes: []*BrowserPane{rootPane},
310 activePane: rootPane,
311 webView: rootView,
312 }
313
314 wm := &WorkspaceManager{
315 app: app,
316 window: rootView.Window(),
317 viewToNode: make(map[*webkit.WebView]*paneNode),
318 lastSplitMsg: make(map[*webkit.WebView]time.Time),
319 lastExitMsg: make(map[*webkit.WebView]time.Time),
320 }
321
322 wm.createWebViewFn = func() (*webkit.WebView, error) {
323 return webkit.NewWebView(&webkit.Config{})
324 }
325
326 wm.createPaneFn = func(view *webkit.WebView) (*BrowserPane, error) {
327 return &BrowserPane{
328 webView: view,
329 id: "bench-pane",
330 }, nil
331 }
332
333 root := &paneNode{
334 pane: rootPane,
335 container: rootView.RootWidget(),
336 isLeaf: true,
337 }
338 wm.root = root
339 wm.active = root
340 wm.mainPane = root
341 wm.viewToNode[rootView] = root
342
343 return wm
344}