v0.24.0 a261e52

profile_test.go RawHistoryFolder

1package config
2
3import (
4	"runtime"
5	"testing"
6
7	"github.com/bnema/dumber/internal/application/port"
8)
9
10func TestResolvePerformanceProfile_Default(t *testing.T) {
11	cfg := &PerformanceConfig{
12		Profile: ProfileDefault,
13	}
14
15	result := ResolvePerformanceProfile(cfg, nil)
16
17	// Default should have no tuning (unset values)
18	if result.SkiaCPUPaintingThreads != 0 {
19		t.Errorf("expected SkiaCPUPaintingThreads=0, got %d", result.SkiaCPUPaintingThreads)
20	}
21	if result.SkiaGPUPaintingThreads != -1 {
22		t.Errorf("expected SkiaGPUPaintingThreads=-1, got %d", result.SkiaGPUPaintingThreads)
23	}
24	if result.WebProcessMemoryLimitMB != 0 {
25		t.Errorf("expected WebProcessMemoryLimitMB=0, got %d", result.WebProcessMemoryLimitMB)
26	}
27	if result.WebViewPoolPrewarmCount != 4 {
28		t.Errorf("expected WebViewPoolPrewarmCount=4, got %d", result.WebViewPoolPrewarmCount)
29	}
30}
31
32func TestResolvePerformanceProfile_Empty(t *testing.T) {
33	cfg := &PerformanceConfig{
34		Profile: "", // Empty should behave like default
35	}
36
37	result := ResolvePerformanceProfile(cfg, nil)
38
39	if result.SkiaCPUPaintingThreads != 0 {
40		t.Errorf("expected SkiaCPUPaintingThreads=0, got %d", result.SkiaCPUPaintingThreads)
41	}
42	if result.WebViewPoolPrewarmCount != 4 {
43		t.Errorf("expected WebViewPoolPrewarmCount=4, got %d", result.WebViewPoolPrewarmCount)
44	}
45}
46
47func TestResolvePerformanceProfile_Lite_NoHardware(t *testing.T) {
48	cfg := &PerformanceConfig{
49		Profile: ProfileLite,
50	}
51
52	result := ResolvePerformanceProfile(cfg, nil)
53
54	// Lite without hardware info should use default lite values
55	if result.SkiaCPUPaintingThreads != 2 {
56		t.Errorf("expected SkiaCPUPaintingThreads=2, got %d", result.SkiaCPUPaintingThreads)
57	}
58	if result.WebProcessMemoryLimitMB != 768 {
59		t.Errorf("expected WebProcessMemoryLimitMB=768, got %d", result.WebProcessMemoryLimitMB)
60	}
61	if result.NetworkProcessMemoryLimitMB != 384 {
62		t.Errorf("expected NetworkProcessMemoryLimitMB=384, got %d", result.NetworkProcessMemoryLimitMB)
63	}
64	if result.WebViewPoolPrewarmCount != 2 {
65		t.Errorf("expected WebViewPoolPrewarmCount=2, got %d", result.WebViewPoolPrewarmCount)
66	}
67}
68
69func TestResolvePerformanceProfile_Lite_LowCoreSystem(t *testing.T) {
70	cfg := &PerformanceConfig{
71		Profile: ProfileLite,
72	}
73	hw := &port.HardwareInfo{
74		CPUCores:   2, // Very low core count
75		CPUThreads: 4,
76	}
77
78	result := ResolvePerformanceProfile(cfg, hw)
79
80	// Lite on low-core system should scale down
81	if result.SkiaCPUPaintingThreads != 1 {
82		t.Errorf("expected SkiaCPUPaintingThreads=1 for 2-core system, got %d", result.SkiaCPUPaintingThreads)
83	}
84}
85
86func TestResolvePerformanceProfile_Max_NoHardware(t *testing.T) {
87	cfg := &PerformanceConfig{
88		Profile: ProfileMax,
89	}
90
91	result := ResolvePerformanceProfile(cfg, nil)
92
93	// Max without hardware info should use fallback values
94	// CPU threads: NumCPU/2 (assuming hyperthreading), clamped to [4, 8]
95	expectedCPUThreads := runtime.NumCPU() / 2
96	if expectedCPUThreads < 4 {
97		expectedCPUThreads = 4
98	}
99	if expectedCPUThreads > 8 {
100		expectedCPUThreads = 8
101	}
102	if result.SkiaCPUPaintingThreads != expectedCPUThreads {
103		t.Errorf("expected SkiaCPUPaintingThreads=%d, got %d", expectedCPUThreads, result.SkiaCPUPaintingThreads)
104	}
105	// GPU threads: conservative default when no VRAM info
106	if result.SkiaGPUPaintingThreads != 2 {
107		t.Errorf("expected SkiaGPUPaintingThreads=2 (fallback), got %d", result.SkiaGPUPaintingThreads)
108	}
109	// Max profile: no memory limits (unset)
110	if result.WebProcessMemoryLimitMB != 0 {
111		t.Errorf("expected WebProcessMemoryLimitMB=0 (unset), got %d", result.WebProcessMemoryLimitMB)
112	}
113	if result.WebViewPoolPrewarmCount != 8 {
114		t.Errorf("expected WebViewPoolPrewarmCount=8, got %d", result.WebViewPoolPrewarmCount)
115	}
116}
117
118func TestResolvePerformanceProfile_Max_HighEndSystem(t *testing.T) {
119	cfg := &PerformanceConfig{
120		Profile: ProfileMax,
121	}
122	hw := &port.HardwareInfo{
123		CPUCores:   16,
124		CPUThreads: 32,
125		TotalRAM:   64 * 1024 * 1024 * 1024, // 64 GB
126		VRAM:       16 * 1024 * 1024 * 1024, // 16 GB (high-end GPU)
127		GPUVendor:  port.GPUVendorAMD,
128	}
129
130	result := ResolvePerformanceProfile(cfg, hw)
131
132	// CPU threads: use physical cores capped at 8 (WebKitGTK limit)
133	if result.SkiaCPUPaintingThreads != 8 {
134		t.Errorf("expected SkiaCPUPaintingThreads=8 (capped), got %d", result.SkiaCPUPaintingThreads)
135	}
136	// GPU threads: high-end tier (16GB+ VRAM)
137	if result.SkiaGPUPaintingThreads != 8 {
138		t.Errorf("expected SkiaGPUPaintingThreads=8 for 16GB VRAM, got %d", result.SkiaGPUPaintingThreads)
139	}
140	// Max profile: no memory limits (unset)
141	if result.WebProcessMemoryLimitMB != 0 {
142		t.Errorf("expected WebProcessMemoryLimitMB=0 (unset) for max profile, got %d", result.WebProcessMemoryLimitMB)
143	}
144	if result.NetworkProcessMemoryLimitMB != 0 {
145		t.Errorf("expected NetworkProcessMemoryLimitMB=0 (unset) for max profile, got %d", result.NetworkProcessMemoryLimitMB)
146	}
147	// Pool prewarm: high-end tier
148	if result.WebViewPoolPrewarmCount != 12 {
149		t.Errorf("expected WebViewPoolPrewarmCount=12 for 64GB RAM, got %d", result.WebViewPoolPrewarmCount)
150	}
151}
152
153func TestResolvePerformanceProfile_Max_MidRangeSystem(t *testing.T) {
154	cfg := &PerformanceConfig{
155		Profile: ProfileMax,
156	}
157	hw := &port.HardwareInfo{
158		CPUCores:   8,
159		CPUThreads: 16,
160		TotalRAM:   16 * 1024 * 1024 * 1024, // 16 GB
161		VRAM:       8 * 1024 * 1024 * 1024,  // 8 GB (mid-range GPU)
162		GPUVendor:  port.GPUVendorNVIDIA,
163	}
164
165	result := ResolvePerformanceProfile(cfg, hw)
166
167	// CPU threads: use physical cores (8)
168	if result.SkiaCPUPaintingThreads != 8 {
169		t.Errorf("expected SkiaCPUPaintingThreads=8, got %d", result.SkiaCPUPaintingThreads)
170	}
171	// GPU threads: mid-range tier (8-16GB VRAM)
172	if result.SkiaGPUPaintingThreads != 6 {
173		t.Errorf("expected SkiaGPUPaintingThreads=6 for 8GB VRAM, got %d", result.SkiaGPUPaintingThreads)
174	}
175	// Max profile: no memory limits (unset)
176	if result.WebProcessMemoryLimitMB != 0 {
177		t.Errorf("expected WebProcessMemoryLimitMB=0 (unset) for max profile, got %d", result.WebProcessMemoryLimitMB)
178	}
179	// Pool prewarm: mid-range tier
180	if result.WebViewPoolPrewarmCount != 8 {
181		t.Errorf("expected WebViewPoolPrewarmCount=8 for 16GB RAM, got %d", result.WebViewPoolPrewarmCount)
182	}
183}
184
185func TestResolvePerformanceProfile_Max_LowEndSystem(t *testing.T) {
186	cfg := &PerformanceConfig{
187		Profile: ProfileMax,
188	}
189	hw := &port.HardwareInfo{
190		CPUCores:   4,
191		CPUThreads: 4,
192		TotalRAM:   4 * 1024 * 1024 * 1024, // 4 GB
193		VRAM:       2 * 1024 * 1024 * 1024, // 2 GB (integrated/old discrete)
194		GPUVendor:  port.GPUVendorIntel,
195	}
196
197	result := ResolvePerformanceProfile(cfg, hw)
198
199	// CPU threads: use physical cores (4)
200	if result.SkiaCPUPaintingThreads != 4 {
201		t.Errorf("expected SkiaCPUPaintingThreads=4, got %d", result.SkiaCPUPaintingThreads)
202	}
203	// GPU threads: low tier (<4GB VRAM)
204	if result.SkiaGPUPaintingThreads != 2 {
205		t.Errorf("expected SkiaGPUPaintingThreads=2 for 2GB VRAM, got %d", result.SkiaGPUPaintingThreads)
206	}
207	// Max profile: no memory limits (unset)
208	if result.WebProcessMemoryLimitMB != 0 {
209		t.Errorf("expected WebProcessMemoryLimitMB=0 (unset) for max profile, got %d", result.WebProcessMemoryLimitMB)
210	}
211	// Pool prewarm: low tier
212	if result.WebViewPoolPrewarmCount != 4 {
213		t.Errorf("expected WebViewPoolPrewarmCount=4 for 4GB RAM, got %d", result.WebViewPoolPrewarmCount)
214	}
215}
216
217func TestResolvePerformanceProfile_Custom(t *testing.T) {
218	cfg := &PerformanceConfig{
219		Profile:                               ProfileCustom,
220		SkiaCPUPaintingThreads:                6,
221		SkiaGPUPaintingThreads:                3,
222		SkiaEnableCPURendering:                true,
223		WebProcessMemoryLimitMB:               1024,
224		WebProcessMemoryConservativeThreshold: 0.4,
225		WebViewPoolPrewarmCount:               5,
226	}
227	// Hardware info should be ignored for custom profile
228	hw := &port.HardwareInfo{
229		CPUCores:   16,
230		CPUThreads: 32,
231		TotalRAM:   64 * 1024 * 1024 * 1024,
232		VRAM:       16 * 1024 * 1024 * 1024,
233	}
234
235	result := ResolvePerformanceProfile(cfg, hw)
236
237	// Custom should use user-specified values, ignoring hardware
238	if result.SkiaCPUPaintingThreads != 6 {
239		t.Errorf("expected SkiaCPUPaintingThreads=6, got %d", result.SkiaCPUPaintingThreads)
240	}
241	if result.SkiaGPUPaintingThreads != 3 {
242		t.Errorf("expected SkiaGPUPaintingThreads=3, got %d", result.SkiaGPUPaintingThreads)
243	}
244	if !result.SkiaEnableCPURendering {
245		t.Error("expected SkiaEnableCPURendering=true")
246	}
247	if result.WebProcessMemoryLimitMB != 1024 {
248		t.Errorf("expected WebProcessMemoryLimitMB=1024, got %d", result.WebProcessMemoryLimitMB)
249	}
250	if result.WebViewPoolPrewarmCount != 5 {
251		t.Errorf("expected WebViewPoolPrewarmCount=5, got %d", result.WebViewPoolPrewarmCount)
252	}
253}
254
255func TestHasCustomPerformanceFields(t *testing.T) {
256	tests := []struct {
257		name     string
258		cfg      PerformanceConfig
259		expected bool
260	}{
261		{
262			name: "all defaults - no custom fields",
263			cfg: PerformanceConfig{
264				SkiaCPUPaintingThreads: 0,
265				SkiaGPUPaintingThreads: -1,
266				SkiaEnableCPURendering: false,
267			},
268			expected: false,
269		},
270		{
271			name: "skia cpu threads set",
272			cfg: PerformanceConfig{
273				SkiaCPUPaintingThreads: 4,
274				SkiaGPUPaintingThreads: -1,
275			},
276			expected: true,
277		},
278		{
279			name: "memory limit set",
280			cfg: PerformanceConfig{
281				SkiaGPUPaintingThreads:  -1,
282				WebProcessMemoryLimitMB: 1024,
283			},
284			expected: true,
285		},
286		{
287			name: "conservative threshold set",
288			cfg: PerformanceConfig{
289				SkiaGPUPaintingThreads:                -1,
290				WebProcessMemoryConservativeThreshold: 0.4,
291			},
292			expected: true,
293		},
294	}
295
296	for _, tt := range tests {
297		t.Run(tt.name, func(t *testing.T) {
298			result := HasCustomPerformanceFields(&tt.cfg)
299			if result != tt.expected {
300				t.Errorf("expected %v, got %v", tt.expected, result)
301			}
302		})
303	}
304}
305
306func TestIsValidPerformanceProfile(t *testing.T) {
307	tests := []struct {
308		profile  PerformanceProfile
309		expected bool
310	}{
311		{ProfileDefault, true},
312		{ProfileLite, true},
313		{ProfileMax, true},
314		{ProfileCustom, true},
315		{"", true}, // Empty is valid (treated as default)
316		{"invalid", false},
317		{"LITE", false}, // Case sensitive
318		{"Maximum", false},
319	}
320
321	for _, tt := range tests {
322		t.Run(string(tt.profile), func(t *testing.T) {
323			result := IsValidPerformanceProfile(tt.profile)
324			if result != tt.expected {
325				t.Errorf("IsValidPerformanceProfile(%q): expected %v, got %v", tt.profile, tt.expected, result)
326			}
327		})
328	}
329}
330
331func TestComputeMaxGPUThreads_VRAMTiers(t *testing.T) {
332	tests := []struct {
333		name     string
334		vramGB   int
335		expected int
336	}{
337		{"no VRAM info", 0, 2},
338		{"2GB VRAM (integrated)", 2, 2},
339		{"4GB VRAM (entry discrete)", 4, 4},
340		{"6GB VRAM (low-mid discrete)", 6, 4},
341		{"8GB VRAM (mid discrete)", 8, 6},
342		{"12GB VRAM (mid-high discrete)", 12, 6},
343		{"16GB VRAM (high-end)", 16, 8},
344		{"24GB VRAM (workstation)", 24, 8},
345	}
346
347	for _, tt := range tests {
348		t.Run(tt.name, func(t *testing.T) {
349			hw := &port.HardwareInfo{
350				VRAM: uint64(tt.vramGB) * 1024 * 1024 * 1024,
351			}
352			if tt.vramGB == 0 {
353				hw = nil
354			}
355			result := computeMaxGPUThreads(hw)
356			if result != tt.expected {
357				t.Errorf("computeMaxGPUThreads(%dGB VRAM): expected %d, got %d", tt.vramGB, tt.expected, result)
358			}
359		})
360	}
361}
362
363func TestComputeMaxMemoryLimits_RAMTiers(t *testing.T) {
364	tests := []struct {
365		name         string
366		ramGB        int
367		expectedWeb  int
368		expectedNet  int
369		expectedPool int
370	}{
371		{"no RAM info", 0, 2048, 512, 8},
372		{"4GB RAM", 4, 1024, 256, 4},
373		{"8GB RAM", 8, 2048, 512, 6},
374		{"16GB RAM", 16, 3072, 768, 8},
375		{"32GB RAM", 32, 4096, 1024, 12},
376		{"64GB RAM", 64, 4096, 1024, 12},
377	}
378
379	for _, tt := range tests {
380		t.Run(tt.name, func(t *testing.T) {
381			hw := &port.HardwareInfo{
382				TotalRAM: uint64(tt.ramGB) * 1024 * 1024 * 1024,
383			}
384			if tt.ramGB == 0 {
385				hw = nil
386			}
387			webMB, netMB := computeMaxMemoryLimits(hw)
388			poolPrewarm := computeMaxPoolPrewarm(hw)
389
390			if webMB != tt.expectedWeb {
391				t.Errorf("computeMaxMemoryLimits(%dGB RAM) web: expected %d, got %d", tt.ramGB, tt.expectedWeb, webMB)
392			}
393			if netMB != tt.expectedNet {
394				t.Errorf("computeMaxMemoryLimits(%dGB RAM) net: expected %d, got %d", tt.ramGB, tt.expectedNet, netMB)
395			}
396			if poolPrewarm != tt.expectedPool {
397				t.Errorf("computeMaxPoolPrewarm(%dGB RAM): expected %d, got %d", tt.ramGB, tt.expectedPool, poolPrewarm)
398			}
399		})
400	}
401}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close