1package config
2
3import (
4 "os"
5 "path/filepath"
6 "testing"
7
8 "github.com/pelletier/go-toml/v2"
9 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/require"
11)
12
13const oldFormatConfig = `
14[performance]
15profile = "balanced"
16zoom_cache_size = 50
17webview_pool_prewarm_count = 2
18skia_cpu_painting_threads = 4
19skia_gpu_painting_threads = 2
20skia_enable_cpu_rendering = false
21web_process_memory_limit_mb = 512
22web_process_memory_poll_interval_sec = 5.0
23web_process_memory_conservative_threshold = 0.7
24web_process_memory_strict_threshold = 0.9
25network_process_memory_limit_mb = 256
26network_process_memory_poll_interval_sec = 10.0
27network_process_memory_conservative_threshold = 0.6
28network_process_memory_strict_threshold = 0.85
29
30[privacy]
31cookie_policy = "accept_all"
32itp_enabled = true
33
34[rendering]
35mode = "auto"
36disable_dmabuf_renderer = false
37force_compositing_mode = true
38disable_compositing_mode = false
39gsk_renderer = "gl"
40disable_mipmaps = false
41prefer_gl = true
42draw_compositing_indicators = false
43show_fps = false
44sample_memory = false
45debug_frames = false
46
47[media]
48hardware_decoding = true
49prefer_av1 = false
50show_diagnostics = false
51force_vsync = true
52gl_rendering_mode = "auto"
53gstreamer_debug_level = 2
54
55[runtime]
56prefix = "/usr/local"
57`
58
59func TestMigrateToEngineConfig_OldFormat(t *testing.T) {
60 tmpDir := t.TempDir()
61 configFile := filepath.Join(tmpDir, "config.toml")
62
63 err := os.WriteFile(configFile, []byte(oldFormatConfig), 0o644)
64 require.NoError(t, err)
65
66 migrated, err := MigrateToEngineConfig(configFile)
67 require.NoError(t, err)
68 assert.True(t, migrated, "should report migration performed")
69
70 // Read the migrated config
71 data, err := os.ReadFile(configFile)
72 require.NoError(t, err)
73
74 var raw map[string]any
75 err = toml.Unmarshal(data, &raw)
76 require.NoError(t, err)
77
78 // [engine] section must exist
79 engineAny, ok := raw["engine"]
80 require.True(t, ok, "engine section must exist")
81 engine, ok := engineAny.(map[string]any)
82 require.True(t, ok, "engine must be a map")
83
84 // legacy engine migration preserves WebKit because the old rendering/media sections are WebKit-specific
85 assert.Equal(t, "webkit", engine["type"])
86
87 // [performance] -> [engine] universal fields
88 assert.Equal(t, "balanced", engine["profile"])
89 assert.Equal(t, int64(50), engine["zoom_cache_size"])
90 assert.Equal(t, int64(2), engine["pool_prewarm_count"])
91
92 // [privacy] -> [engine] universal field, translated to canonical schema value
93 assert.Equal(t, "always", engine["cookie_policy"])
94
95 // [engine.webkit] section
96 webkitAny, ok := engine["webkit"]
97 require.True(t, ok, "engine.webkit section must exist")
98 webkit, ok := webkitAny.(map[string]any)
99 require.True(t, ok, "engine.webkit must be a map")
100
101 // [privacy] -> [engine.webkit]
102 assert.Equal(t, true, webkit["itp_enabled"])
103
104 // [rendering] -> [engine.webkit] (excluding mode which is dropped)
105 assert.Equal(t, false, webkit["disable_dmabuf_renderer"])
106 assert.Equal(t, true, webkit["force_compositing_mode"])
107 assert.Equal(t, false, webkit["disable_compositing_mode"])
108 assert.Equal(t, "gl", webkit["gsk_renderer"])
109 assert.Equal(t, false, webkit["disable_mipmaps"])
110 assert.Equal(t, true, webkit["prefer_gl"])
111 assert.Equal(t, false, webkit["draw_compositing_indicators"])
112 assert.Equal(t, false, webkit["show_fps"])
113 assert.Equal(t, false, webkit["sample_memory"])
114 assert.Equal(t, false, webkit["debug_frames"])
115 // rendering.mode must be dropped (not present in webkit)
116 assert.NotContains(t, webkit, "mode")
117
118 // [performance] -> [engine.webkit] (webkit-specific)
119 assert.Equal(t, int64(4), webkit["skia_cpu_painting_threads"])
120 assert.Equal(t, int64(2), webkit["skia_gpu_painting_threads"])
121 assert.Equal(t, false, webkit["skia_enable_cpu_rendering"])
122 assert.Equal(t, int64(512), webkit["web_process_memory_limit_mb"])
123 assert.InDelta(t, 5.0, webkit["web_process_memory_poll_interval_sec"], 0.001)
124 assert.InDelta(t, 0.7, webkit["web_process_memory_conservative_threshold"], 0.001)
125 assert.InDelta(t, 0.9, webkit["web_process_memory_strict_threshold"], 0.001)
126 assert.Equal(t, int64(256), webkit["network_process_memory_limit_mb"])
127 assert.InDelta(t, 10.0, webkit["network_process_memory_poll_interval_sec"], 0.001)
128 assert.InDelta(t, 0.6, webkit["network_process_memory_conservative_threshold"], 0.001)
129 assert.InDelta(t, 0.85, webkit["network_process_memory_strict_threshold"], 0.001)
130
131 // [media] GStreamer fields -> [engine.webkit]
132 assert.Equal(t, true, webkit["force_vsync"])
133 assert.Equal(t, "auto", webkit["gl_rendering_mode"])
134 assert.Equal(t, int64(2), webkit["gstreamer_debug_level"])
135
136 // [runtime] -> [engine.webkit]
137 assert.Equal(t, "/usr/local", webkit["prefix"])
138
139 // Old top-level sections must be removed
140 assert.NotContains(t, raw, "performance")
141 assert.NotContains(t, raw, "rendering")
142 assert.NotContains(t, raw, "runtime")
143
144 // [privacy] should be removed (all fields migrated)
145 assert.NotContains(t, raw, "privacy")
146}
147
148func TestMigrateToEngineConfig_AlreadyMigrated(t *testing.T) {
149 tmpDir := t.TempDir()
150 configFile := filepath.Join(tmpDir, "config.toml")
151
152 alreadyMigrated := `
153[engine]
154type = "webkit"
155profile = "balanced"
156
157[engine.webkit]
158itp_enabled = true
159`
160 err := os.WriteFile(configFile, []byte(alreadyMigrated), 0o644)
161 require.NoError(t, err)
162
163 migrated, err := MigrateToEngineConfig(configFile)
164 require.NoError(t, err)
165 assert.False(t, migrated, "should not migrate when already migrated")
166
167 // Content should be unchanged
168 data, err := os.ReadFile(configFile)
169 require.NoError(t, err)
170 assert.Equal(t, alreadyMigrated, string(data))
171}
172
173func TestMigrateToEngineConfig_NoOldSections(t *testing.T) {
174 tmpDir := t.TempDir()
175 configFile := filepath.Join(tmpDir, "config.toml")
176
177 noOldSections := `
178[history]
179max_entries = 1000
180
181[logging]
182level = "info"
183`
184 err := os.WriteFile(configFile, []byte(noOldSections), 0o644)
185 require.NoError(t, err)
186
187 migrated, err := MigrateToEngineConfig(configFile)
188 require.NoError(t, err)
189 assert.False(t, migrated, "should not migrate when no old sections exist")
190
191 // Content should be unchanged
192 data, err := os.ReadFile(configFile)
193 require.NoError(t, err)
194 assert.Equal(t, noOldSections, string(data))
195}
196
197func TestMigrateToEngineConfig_PreservesNonMigratedMediaFields(t *testing.T) {
198 tmpDir := t.TempDir()
199 configFile := filepath.Join(tmpDir, "config.toml")
200
201 content := `
202[media]
203hardware_decoding = true
204prefer_av1 = false
205show_diagnostics = true
206force_vsync = true
207gl_rendering_mode = "auto"
208gstreamer_debug_level = 3
209
210[rendering]
211disable_dmabuf_renderer = false
212`
213 err := os.WriteFile(configFile, []byte(content), 0o644)
214 require.NoError(t, err)
215
216 migrated, err := MigrateToEngineConfig(configFile)
217 require.NoError(t, err)
218 assert.True(t, migrated)
219
220 data, err := os.ReadFile(configFile)
221 require.NoError(t, err)
222
223 var raw map[string]any
224 err = toml.Unmarshal(data, &raw)
225 require.NoError(t, err)
226
227 // media section must still exist with non-migrated fields
228 mediaAny, ok := raw["media"]
229 require.True(t, ok, "media section must still exist")
230 media, ok := mediaAny.(map[string]any)
231 require.True(t, ok)
232
233 assert.Equal(t, true, media["hardware_decoding"])
234 assert.Equal(t, false, media["prefer_av1"])
235 assert.Equal(t, true, media["show_diagnostics"])
236
237 // Migrated media fields should be removed from media
238 assert.NotContains(t, media, "force_vsync")
239 assert.NotContains(t, media, "gl_rendering_mode")
240 assert.NotContains(t, media, "gstreamer_debug_level")
241
242 // Migrated fields should be in engine.webkit
243 engineAny, ok := raw["engine"]
244 require.True(t, ok, "engine section must exist")
245 engine, ok := engineAny.(map[string]any)
246 require.True(t, ok, "engine section must be a map")
247 webkitAny, ok := engine["webkit"]
248 require.True(t, ok, "engine.webkit section must exist")
249 webkit, ok := webkitAny.(map[string]any)
250 require.True(t, ok, "engine.webkit section must be a map")
251 assert.Equal(t, true, webkit["force_vsync"])
252 assert.Equal(t, "auto", webkit["gl_rendering_mode"])
253 assert.Equal(t, int64(3), webkit["gstreamer_debug_level"])
254}
255
256func TestLegacyEngineAliasesMetadataCoverage(t *testing.T) {
257 require.NotEmpty(t, legacyEngineAliases)
258
259 aliases := make(map[string]legacyEngineAlias)
260 for _, alias := range legacyEngineAliases {
261 key := alias.legacyKey()
262 require.NotEmpty(t, alias.legacySection, "%s should have a legacy section", key)
263 require.NotEmpty(t, alias.legacyField, "%s should have a legacy field", key)
264 require.NotContains(t, aliases, key, "duplicate metadata for %s", key)
265 aliases[key] = alias
266
267 if alias.dropped {
268 assert.Empty(t, alias.targetSection, "%s is dropped and should not have a target section", key)
269 assert.Empty(t, alias.targetField, "%s is dropped and should not have a target field", key)
270 continue
271 }
272
273 require.Contains(t, []string{"engine", "engine.webkit"}, alias.targetSection, "%s should target a known engine section", key)
274 require.NotEmpty(t, alias.targetField, "%s should have a canonical target field", key)
275 }
276
277 for _, key := range []string{
278 "media.force_vsync",
279 "media.gl_rendering_mode",
280 "media.gstreamer_debug_level",
281 "runtime.prefix",
282 "performance.profile",
283 "privacy.cookie_policy",
284 "privacy.itp_enabled",
285 "rendering.disable_dmabuf_renderer",
286 } {
287 alias, ok := aliases[key]
288 require.True(t, ok, "missing metadata for %s", key)
289 require.False(t, alias.dropped, "%s should have a canonical target", key)
290 }
291
292 renderingMode, ok := aliases["rendering.mode"]
293 require.True(t, ok, "missing dropped metadata for rendering.mode")
294 assert.True(t, renderingMode.dropped)
295}
296
297func TestHasOldEngineSections_UsesMetadataForMigratedMediaKeys(t *testing.T) {
298 for _, field := range []string{"force_vsync", "gl_rendering_mode", "gstreamer_debug_level"} {
299 raw := map[string]any{"media": map[string]any{field: true}}
300 assert.True(t, hasOldEngineSections(raw), "media.%s should trigger engine migration", field)
301 }
302
303 raw := map[string]any{"media": map[string]any{"hardware_decoding": true}}
304 assert.False(t, hasOldEngineSections(raw), "non-migrated media keys should not trigger engine migration")
305}
306
307func TestMigrateToEngineConfig_MixedEngineAndMigratedMediaKey(t *testing.T) {
308 tmpDir := t.TempDir()
309 configFile := filepath.Join(tmpDir, "config.toml")
310
311 content := `
312[engine]
313type = "webkit"
314
315[media]
316gl_rendering_mode = "auto"
317`
318 err := os.WriteFile(configFile, []byte(content), 0o644)
319 require.NoError(t, err)
320
321 migrated, err := MigrateToEngineConfig(configFile)
322 require.Error(t, err)
323 assert.False(t, migrated)
324 assert.Contains(t, err.Error(), "media.gl_rendering_mode")
325}
326
327func TestMigrateToEngineConfig_EngineTypeDefault(t *testing.T) {
328 tmpDir := t.TempDir()
329 configFile := filepath.Join(tmpDir, "config.toml")
330
331 // Minimal old-format config to trigger migration
332 content := `
333[rendering]
334show_fps = true
335`
336 err := os.WriteFile(configFile, []byte(content), 0o644)
337 require.NoError(t, err)
338
339 migrated, err := MigrateToEngineConfig(configFile)
340 require.NoError(t, err)
341 assert.True(t, migrated)
342
343 data, err := os.ReadFile(configFile)
344 require.NoError(t, err)
345
346 var raw map[string]any
347 err = toml.Unmarshal(data, &raw)
348 require.NoError(t, err)
349
350 engineAny, ok := raw["engine"]
351 require.True(t, ok, "raw[\"engine\"] should exist")
352 engine, ok := engineAny.(map[string]any)
353 require.True(t, ok, "raw[\"engine\"] should be map[string]any, got %T", engineAny)
354 assert.Equal(t, EngineTypeWebKit, engine["type"], "legacy engine migration should preserve WebKit")
355}