feat(config): add MigrateToEngineConfig for [engine]/[engine.webkit] restructure
Implements a pre-migration step that reads old config sections ([performance], [privacy], [rendering], [runtime] and media GStreamer fields) and restructures them into the new [engine]/[engine.webkit] format, with engine.type defaulting to "webkit".
- commit
- 29ea63e474b21a0563b79028f56e4f7d7d62827f
- author
- brice · · 2026-03-15 16:09 UTC
- parent
- a49a8b0
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +467 -0
A internal/infrastructure/config/migrate_engine.go +192 -0
| @@ -0,0 +1,192 @@ | |||
| 1 | + | package config | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "os" | |
| 5 | + | ||
| 6 | + | "github.com/pelletier/go-toml/v2" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // engineMigrationMapping defines old key -> new key mappings for the engine migration. | |
| 10 | + | // Keys with empty new key ("") are dropped (not migrated). | |
| 11 | + | // The source section is the first part of the old key. | |
| 12 | + | type engineFieldMapping struct { | |
| 13 | + | oldSection string | |
| 14 | + | oldField string | |
| 15 | + | newSection string // "engine" or "engine.webkit" | |
| 16 | + | newField string // empty means drop | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | // engineUniversalMappings are fields that go into [engine] (not [engine.webkit]). | |
| 20 | + | var engineUniversalMappings = []engineFieldMapping{ | |
| 21 | + | // [performance] -> [engine] | |
| 22 | + | {oldSection: "performance", oldField: "profile", newSection: "engine", newField: "profile"}, | |
| 23 | + | {oldSection: "performance", oldField: "zoom_cache_size", newSection: "engine", newField: "zoom_cache_size"}, | |
| 24 | + | {oldSection: "performance", oldField: "webview_pool_prewarm_count", newSection: "engine", newField: "pool_prewarm_count"}, | |
| 25 | + | // [privacy] -> [engine] | |
| 26 | + | {oldSection: "privacy", oldField: "cookie_policy", newSection: "engine", newField: "cookie_policy"}, | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | // engineWebkitMappings are fields that go into [engine.webkit]. | |
| 30 | + | var engineWebkitMappings = []engineFieldMapping{ | |
| 31 | + | // [privacy] -> [engine.webkit] | |
| 32 | + | {oldSection: "privacy", oldField: "itp_enabled", newSection: "engine.webkit", newField: "itp_enabled"}, | |
| 33 | + | ||
| 34 | + | // [rendering] -> [engine.webkit] (rendering.mode is dropped) | |
| 35 | + | {oldSection: "rendering", oldField: "disable_dmabuf_renderer", newSection: "engine.webkit", newField: "disable_dmabuf_renderer"}, | |
| 36 | + | {oldSection: "rendering", oldField: "force_compositing_mode", newSection: "engine.webkit", newField: "force_compositing_mode"}, | |
| 37 | + | {oldSection: "rendering", oldField: "disable_compositing_mode", newSection: "engine.webkit", newField: "disable_compositing_mode"}, | |
| 38 | + | {oldSection: "rendering", oldField: "gsk_renderer", newSection: "engine.webkit", newField: "gsk_renderer"}, | |
| 39 | + | {oldSection: "rendering", oldField: "disable_mipmaps", newSection: "engine.webkit", newField: "disable_mipmaps"}, | |
| 40 | + | {oldSection: "rendering", oldField: "prefer_gl", newSection: "engine.webkit", newField: "prefer_gl"}, | |
| 41 | + | {oldSection: "rendering", oldField: "draw_compositing_indicators", newSection: "engine.webkit", newField: "draw_compositing_indicators"}, | |
| 42 | + | {oldSection: "rendering", oldField: "show_fps", newSection: "engine.webkit", newField: "show_fps"}, | |
| 43 | + | {oldSection: "rendering", oldField: "sample_memory", newSection: "engine.webkit", newField: "sample_memory"}, | |
| 44 | + | {oldSection: "rendering", oldField: "debug_frames", newSection: "engine.webkit", newField: "debug_frames"}, | |
| 45 | + | ||
| 46 | + | // [performance] -> [engine.webkit] (webkit-specific) | |
| 47 | + | {oldSection: "performance", oldField: "skia_cpu_painting_threads", newSection: "engine.webkit", newField: "skia_cpu_painting_threads"}, | |
| 48 | + | {oldSection: "performance", oldField: "skia_gpu_painting_threads", newSection: "engine.webkit", newField: "skia_gpu_painting_threads"}, | |
| 49 | + | {oldSection: "performance", oldField: "skia_enable_cpu_rendering", newSection: "engine.webkit", newField: "skia_enable_cpu_rendering"}, | |
| 50 | + | {oldSection: "performance", oldField: "web_process_memory_limit_mb", newSection: "engine.webkit", newField: "web_process_memory_limit_mb"}, | |
| 51 | + | {oldSection: "performance", oldField: "web_process_memory_poll_interval_sec", newSection: "engine.webkit", newField: "web_process_memory_poll_interval_sec"}, | |
| 52 | + | {oldSection: "performance", oldField: "web_process_memory_conservative_threshold", newSection: "engine.webkit", newField: "web_process_memory_conservative_threshold"}, | |
| 53 | + | {oldSection: "performance", oldField: "web_process_memory_strict_threshold", newSection: "engine.webkit", newField: "web_process_memory_strict_threshold"}, | |
| 54 | + | {oldSection: "performance", oldField: "network_process_memory_limit_mb", newSection: "engine.webkit", newField: "network_process_memory_limit_mb"}, | |
| 55 | + | {oldSection: "performance", oldField: "network_process_memory_poll_interval_sec", newSection: "engine.webkit", newField: "network_process_memory_poll_interval_sec"}, | |
| 56 | + | {oldSection: "performance", oldField: "network_process_memory_conservative_threshold", newSection: "engine.webkit", newField: "network_process_memory_conservative_threshold"}, | |
| 57 | + | {oldSection: "performance", oldField: "network_process_memory_strict_threshold", newSection: "engine.webkit", newField: "network_process_memory_strict_threshold"}, | |
| 58 | + | ||
| 59 | + | // [media] GStreamer fields -> [engine.webkit] | |
| 60 | + | {oldSection: "media", oldField: "force_vsync", newSection: "engine.webkit", newField: "force_vsync"}, | |
| 61 | + | {oldSection: "media", oldField: "gl_rendering_mode", newSection: "engine.webkit", newField: "gl_rendering_mode"}, | |
| 62 | + | {oldSection: "media", oldField: "gstreamer_debug_level", newSection: "engine.webkit", newField: "gstreamer_debug_level"}, | |
| 63 | + | ||
| 64 | + | // [runtime] -> [engine.webkit] | |
| 65 | + | {oldSection: "runtime", oldField: "prefix", newSection: "engine.webkit", newField: "prefix"}, | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | // oldSectionsToRemove are the sections that are fully removed after migration | |
| 69 | + | // (media is not here because it has fields that stay). | |
| 70 | + | var oldSectionsToRemove = []string{"rendering", "performance", "privacy", "runtime"} | |
| 71 | + | ||
| 72 | + | // MigrateToEngineConfig restructures old top-level config sections into [engine]/[engine.webkit]. | |
| 73 | + | // Returns true if migration was performed, false if already migrated or no old sections found. | |
| 74 | + | func MigrateToEngineConfig(configFile string) (bool, error) { | |
| 75 | + | data, err := os.ReadFile(configFile) | |
| 76 | + | if err != nil { | |
| 77 | + | return false, err | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | var raw map[string]any | |
| 81 | + | if err := toml.Unmarshal(data, &raw); err != nil { | |
| 82 | + | return false, err | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | // If "engine" key already exists, already migrated. | |
| 86 | + | if _, exists := raw["engine"]; exists { | |
| 87 | + | return false, nil | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | // Check if any old sections exist. | |
| 91 | + | hasOldSections := false | |
| 92 | + | for _, section := range []string{"rendering", "performance", "privacy", "runtime"} { | |
| 93 | + | if _, exists := raw[section]; exists { | |
| 94 | + | hasOldSections = true | |
| 95 | + | break | |
| 96 | + | } | |
| 97 | + | } | |
| 98 | + | // Also check media for GStreamer fields that migrate. | |
| 99 | + | if !hasOldSections { | |
| 100 | + | if mediaAny, exists := raw["media"]; exists { | |
| 101 | + | if media, ok := mediaAny.(map[string]any); ok { | |
| 102 | + | for _, f := range []string{"force_vsync", "gl_rendering_mode", "gstreamer_debug_level"} { | |
| 103 | + | if _, has := media[f]; has { | |
| 104 | + | hasOldSections = true | |
| 105 | + | break | |
| 106 | + | } | |
| 107 | + | } | |
| 108 | + | } | |
| 109 | + | } | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | if !hasOldSections { | |
| 113 | + | return false, nil | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | // Build engine and engine.webkit maps. | |
| 117 | + | engineMap := make(map[string]any) | |
| 118 | + | webkitMap := make(map[string]any) | |
| 119 | + | ||
| 120 | + | // Apply universal mappings -> engine. | |
| 121 | + | for _, m := range engineUniversalMappings { | |
| 122 | + | if val := getSectionField(raw, m.oldSection, m.oldField); val != nil { | |
| 123 | + | engineMap[m.newField] = val | |
| 124 | + | } | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | // Apply webkit mappings -> engine.webkit. | |
| 128 | + | for _, m := range engineWebkitMappings { | |
| 129 | + | if val := getSectionField(raw, m.oldSection, m.oldField); val != nil { | |
| 130 | + | webkitMap[m.newField] = val | |
| 131 | + | } | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | // Set engine.type default. | |
| 135 | + | engineMap["type"] = "webkit" | |
| 136 | + | ||
| 137 | + | // Attach webkit sub-map. | |
| 138 | + | if len(webkitMap) > 0 { | |
| 139 | + | engineMap["webkit"] = webkitMap | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | // Set engine in raw map. | |
| 143 | + | raw["engine"] = engineMap | |
| 144 | + | ||
| 145 | + | // Remove old sections entirely. | |
| 146 | + | for _, section := range oldSectionsToRemove { | |
| 147 | + | delete(raw, section) | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | // Remove migrated fields from media (but keep non-migrated ones). | |
| 151 | + | if mediaAny, exists := raw["media"]; exists { | |
| 152 | + | if media, ok := mediaAny.(map[string]any); ok { | |
| 153 | + | delete(media, "force_vsync") | |
| 154 | + | delete(media, "gl_rendering_mode") | |
| 155 | + | delete(media, "gstreamer_debug_level") | |
| 156 | + | if len(media) == 0 { | |
| 157 | + | delete(raw, "media") | |
| 158 | + | } else { | |
| 159 | + | raw["media"] = media | |
| 160 | + | } | |
| 161 | + | } | |
| 162 | + | } | |
| 163 | + | ||
| 164 | + | // Write back. | |
| 165 | + | out, err := toml.Marshal(raw) | |
| 166 | + | if err != nil { | |
| 167 | + | return false, err | |
| 168 | + | } | |
| 169 | + | ||
| 170 | + | if err := os.WriteFile(configFile, out, 0o644); err != nil { | |
| 171 | + | return false, err | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | return true, nil | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | // getSectionField retrieves a field value from a named section in the raw config map. | |
| 178 | + | func getSectionField(raw map[string]any, section, field string) any { | |
| 179 | + | sectionAny, exists := raw[section] | |
| 180 | + | if !exists { | |
| 181 | + | return nil | |
| 182 | + | } | |
| 183 | + | sectionMap, ok := sectionAny.(map[string]any) | |
| 184 | + | if !ok { | |
| 185 | + | return nil | |
| 186 | + | } | |
| 187 | + | val, exists := sectionMap[field] | |
| 188 | + | if !exists { | |
| 189 | + | return nil | |
| 190 | + | } | |
| 191 | + | return val | |
| 192 | + | } | |
| @@ -0,0 +1,192 @@ | |||||
| 1 | + | package config | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "os" | |||
| 5 | + | ||||
| 6 | + | "github.com/pelletier/go-toml/v2" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | // engineMigrationMapping defines old key -> new key mappings for the engine migration. | |||
| 10 | + | // Keys with empty new key ("") are dropped (not migrated). | |||
| 11 | + | // The source section is the first part of the old key. | |||
| 12 | + | type engineFieldMapping struct { | |||
| 13 | + | oldSection string | |||
| 14 | + | oldField string | |||
| 15 | + | newSection string // "engine" or "engine.webkit" | |||
| 16 | + | newField string // empty means drop | |||
| 17 | + | } | |||
| 18 | + | ||||
| 19 | + | // engineUniversalMappings are fields that go into [engine] (not [engine.webkit]). | |||
| 20 | + | var engineUniversalMappings = []engineFieldMapping{ | |||
| 21 | + | // [performance] -> [engine] | |||
| 22 | + | {oldSection: "performance", oldField: "profile", newSection: "engine", newField: "profile"}, | |||
| 23 | + | {oldSection: "performance", oldField: "zoom_cache_size", newSection: "engine", newField: "zoom_cache_size"}, | |||
| 24 | + | {oldSection: "performance", oldField: "webview_pool_prewarm_count", newSection: "engine", newField: "pool_prewarm_count"}, | |||
| 25 | + | // [privacy] -> [engine] | |||
| 26 | + | {oldSection: "privacy", oldField: "cookie_policy", newSection: "engine", newField: "cookie_policy"}, | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | // engineWebkitMappings are fields that go into [engine.webkit]. | |||
| 30 | + | var engineWebkitMappings = []engineFieldMapping{ | |||
| 31 | + | // [privacy] -> [engine.webkit] | |||
| 32 | + | {oldSection: "privacy", oldField: "itp_enabled", newSection: "engine.webkit", newField: "itp_enabled"}, | |||
| 33 | + | ||||
| 34 | + | // [rendering] -> [engine.webkit] (rendering.mode is dropped) | |||
| 35 | + | {oldSection: "rendering", oldField: "disable_dmabuf_renderer", newSection: "engine.webkit", newField: "disable_dmabuf_renderer"}, | |||
| 36 | + | {oldSection: "rendering", oldField: "force_compositing_mode", newSection: "engine.webkit", newField: "force_compositing_mode"}, | |||
| 37 | + | {oldSection: "rendering", oldField: "disable_compositing_mode", newSection: "engine.webkit", newField: "disable_compositing_mode"}, | |||
| 38 | + | {oldSection: "rendering", oldField: "gsk_renderer", newSection: "engine.webkit", newField: "gsk_renderer"}, | |||
| 39 | + | {oldSection: "rendering", oldField: "disable_mipmaps", newSection: "engine.webkit", newField: "disable_mipmaps"}, | |||
| 40 | + | {oldSection: "rendering", oldField: "prefer_gl", newSection: "engine.webkit", newField: "prefer_gl"}, | |||
| 41 | + | {oldSection: "rendering", oldField: "draw_compositing_indicators", newSection: "engine.webkit", newField: "draw_compositing_indicators"}, | |||
| 42 | + | {oldSection: "rendering", oldField: "show_fps", newSection: "engine.webkit", newField: "show_fps"}, | |||
| 43 | + | {oldSection: "rendering", oldField: "sample_memory", newSection: "engine.webkit", newField: "sample_memory"}, | |||
| 44 | + | {oldSection: "rendering", oldField: "debug_frames", newSection: "engine.webkit", newField: "debug_frames"}, | |||
| 45 | + | ||||
| 46 | + | // [performance] -> [engine.webkit] (webkit-specific) | |||
| 47 | + | {oldSection: "performance", oldField: "skia_cpu_painting_threads", newSection: "engine.webkit", newField: "skia_cpu_painting_threads"}, | |||
| 48 | + | {oldSection: "performance", oldField: "skia_gpu_painting_threads", newSection: "engine.webkit", newField: "skia_gpu_painting_threads"}, | |||
| 49 | + | {oldSection: "performance", oldField: "skia_enable_cpu_rendering", newSection: "engine.webkit", newField: "skia_enable_cpu_rendering"}, | |||
| 50 | + | {oldSection: "performance", oldField: "web_process_memory_limit_mb", newSection: "engine.webkit", newField: "web_process_memory_limit_mb"}, | |||
| 51 | + | {oldSection: "performance", oldField: "web_process_memory_poll_interval_sec", newSection: "engine.webkit", newField: "web_process_memory_poll_interval_sec"}, | |||
| 52 | + | {oldSection: "performance", oldField: "web_process_memory_conservative_threshold", newSection: "engine.webkit", newField: "web_process_memory_conservative_threshold"}, | |||
| 53 | + | {oldSection: "performance", oldField: "web_process_memory_strict_threshold", newSection: "engine.webkit", newField: "web_process_memory_strict_threshold"}, | |||
| 54 | + | {oldSection: "performance", oldField: "network_process_memory_limit_mb", newSection: "engine.webkit", newField: "network_process_memory_limit_mb"}, | |||
| 55 | + | {oldSection: "performance", oldField: "network_process_memory_poll_interval_sec", newSection: "engine.webkit", newField: "network_process_memory_poll_interval_sec"}, | |||
| 56 | + | {oldSection: "performance", oldField: "network_process_memory_conservative_threshold", newSection: "engine.webkit", newField: "network_process_memory_conservative_threshold"}, | |||
| 57 | + | {oldSection: "performance", oldField: "network_process_memory_strict_threshold", newSection: "engine.webkit", newField: "network_process_memory_strict_threshold"}, | |||
| 58 | + | ||||
| 59 | + | // [media] GStreamer fields -> [engine.webkit] | |||
| 60 | + | {oldSection: "media", oldField: "force_vsync", newSection: "engine.webkit", newField: "force_vsync"}, | |||
| 61 | + | {oldSection: "media", oldField: "gl_rendering_mode", newSection: "engine.webkit", newField: "gl_rendering_mode"}, | |||
| 62 | + | {oldSection: "media", oldField: "gstreamer_debug_level", newSection: "engine.webkit", newField: "gstreamer_debug_level"}, | |||
| 63 | + | ||||
| 64 | + | // [runtime] -> [engine.webkit] | |||
| 65 | + | {oldSection: "runtime", oldField: "prefix", newSection: "engine.webkit", newField: "prefix"}, | |||
| 66 | + | } | |||
| 67 | + | ||||
| 68 | + | // oldSectionsToRemove are the sections that are fully removed after migration | |||
| 69 | + | // (media is not here because it has fields that stay). | |||
| 70 | + | var oldSectionsToRemove = []string{"rendering", "performance", "privacy", "runtime"} | |||
| 71 | + | ||||
| 72 | + | // MigrateToEngineConfig restructures old top-level config sections into [engine]/[engine.webkit]. | |||
| 73 | + | // Returns true if migration was performed, false if already migrated or no old sections found. | |||
| 74 | + | func MigrateToEngineConfig(configFile string) (bool, error) { | |||
| 75 | + | data, err := os.ReadFile(configFile) | |||
| 76 | + | if err != nil { | |||
| 77 | + | return false, err | |||
| 78 | + | } | |||
| 79 | + | ||||
| 80 | + | var raw map[string]any | |||
| 81 | + | if err := toml.Unmarshal(data, &raw); err != nil { | |||
| 82 | + | return false, err | |||
| 83 | + | } | |||
| 84 | + | ||||
| 85 | + | // If "engine" key already exists, already migrated. | |||
| 86 | + | if _, exists := raw["engine"]; exists { | |||
| 87 | + | return false, nil | |||
| 88 | + | } | |||
| 89 | + | ||||
| 90 | + | // Check if any old sections exist. | |||
| 91 | + | hasOldSections := false | |||
| 92 | + | for _, section := range []string{"rendering", "performance", "privacy", "runtime"} { | |||
| 93 | + | if _, exists := raw[section]; exists { | |||
| 94 | + | hasOldSections = true | |||
| 95 | + | break | |||
| 96 | + | } | |||
| 97 | + | } | |||
| 98 | + | // Also check media for GStreamer fields that migrate. | |||
| 99 | + | if !hasOldSections { | |||
| 100 | + | if mediaAny, exists := raw["media"]; exists { | |||
| 101 | + | if media, ok := mediaAny.(map[string]any); ok { | |||
| 102 | + | for _, f := range []string{"force_vsync", "gl_rendering_mode", "gstreamer_debug_level"} { | |||
| 103 | + | if _, has := media[f]; has { | |||
| 104 | + | hasOldSections = true | |||
| 105 | + | break | |||
| 106 | + | } | |||
| 107 | + | } | |||
| 108 | + | } | |||
| 109 | + | } | |||
| 110 | + | } | |||
| 111 | + | ||||
| 112 | + | if !hasOldSections { | |||
| 113 | + | return false, nil | |||
| 114 | + | } | |||
| 115 | + | ||||
| 116 | + | // Build engine and engine.webkit maps. | |||
| 117 | + | engineMap := make(map[string]any) | |||
| 118 | + | webkitMap := make(map[string]any) | |||
| 119 | + | ||||
| 120 | + | // Apply universal mappings -> engine. | |||
| 121 | + | for _, m := range engineUniversalMappings { | |||
| 122 | + | if val := getSectionField(raw, m.oldSection, m.oldField); val != nil { | |||
| 123 | + | engineMap[m.newField] = val | |||
| 124 | + | } | |||
| 125 | + | } | |||
| 126 | + | ||||
| 127 | + | // Apply webkit mappings -> engine.webkit. | |||
| 128 | + | for _, m := range engineWebkitMappings { | |||
| 129 | + | if val := getSectionField(raw, m.oldSection, m.oldField); val != nil { | |||
| 130 | + | webkitMap[m.newField] = val | |||
| 131 | + | } | |||
| 132 | + | } | |||
| 133 | + | ||||
| 134 | + | // Set engine.type default. | |||
| 135 | + | engineMap["type"] = "webkit" | |||
| 136 | + | ||||
| 137 | + | // Attach webkit sub-map. | |||
| 138 | + | if len(webkitMap) > 0 { | |||
| 139 | + | engineMap["webkit"] = webkitMap | |||
| 140 | + | } | |||
| 141 | + | ||||
| 142 | + | // Set engine in raw map. | |||
| 143 | + | raw["engine"] = engineMap | |||
| 144 | + | ||||
| 145 | + | // Remove old sections entirely. | |||
| 146 | + | for _, section := range oldSectionsToRemove { | |||
| 147 | + | delete(raw, section) | |||
| 148 | + | } | |||
| 149 | + | ||||
| 150 | + | // Remove migrated fields from media (but keep non-migrated ones). | |||
| 151 | + | if mediaAny, exists := raw["media"]; exists { | |||
| 152 | + | if media, ok := mediaAny.(map[string]any); ok { | |||
| 153 | + | delete(media, "force_vsync") | |||
| 154 | + | delete(media, "gl_rendering_mode") | |||
| 155 | + | delete(media, "gstreamer_debug_level") | |||
| 156 | + | if len(media) == 0 { | |||
| 157 | + | delete(raw, "media") | |||
| 158 | + | } else { | |||
| 159 | + | raw["media"] = media | |||
| 160 | + | } | |||
| 161 | + | } | |||
| 162 | + | } | |||
| 163 | + | ||||
| 164 | + | // Write back. | |||
| 165 | + | out, err := toml.Marshal(raw) | |||
| 166 | + | if err != nil { | |||
| 167 | + | return false, err | |||
| 168 | + | } | |||
| 169 | + | ||||
| 170 | + | if err := os.WriteFile(configFile, out, 0o644); err != nil { | |||
| 171 | + | return false, err | |||
| 172 | + | } | |||
| 173 | + | ||||
| 174 | + | return true, nil | |||
| 175 | + | } | |||
| 176 | + | ||||
| 177 | + | // getSectionField retrieves a field value from a named section in the raw config map. | |||
| 178 | + | func getSectionField(raw map[string]any, section, field string) any { | |||
| 179 | + | sectionAny, exists := raw[section] | |||
| 180 | + | if !exists { | |||
| 181 | + | return nil | |||
| 182 | + | } | |||
| 183 | + | sectionMap, ok := sectionAny.(map[string]any) | |||
| 184 | + | if !ok { | |||
| 185 | + | return nil | |||
| 186 | + | } | |||
| 187 | + | val, exists := sectionMap[field] | |||
| 188 | + | if !exists { | |||
| 189 | + | return nil | |||
| 190 | + | } | |||
| 191 | + | return val | |||
| 192 | + | } | |||
A internal/infrastructure/config/migrate_engine_test.go +275 -0
| @@ -0,0 +1,275 @@ | |||
| 1 | + | package config | |
| 2 | + | ||
| 3 | + | import ( | |
| 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 | + | ||
| 13 | + | const oldFormatConfig = ` | |
| 14 | + | [performance] | |
| 15 | + | profile = "balanced" | |
| 16 | + | zoom_cache_size = 50 | |
| 17 | + | webview_pool_prewarm_count = 2 | |
| 18 | + | skia_cpu_painting_threads = 4 | |
| 19 | + | skia_gpu_painting_threads = 2 | |
| 20 | + | skia_enable_cpu_rendering = false | |
| 21 | + | web_process_memory_limit_mb = 512 | |
| 22 | + | web_process_memory_poll_interval_sec = 5.0 | |
| 23 | + | web_process_memory_conservative_threshold = 0.7 | |
| 24 | + | web_process_memory_strict_threshold = 0.9 | |
| 25 | + | network_process_memory_limit_mb = 256 | |
| 26 | + | network_process_memory_poll_interval_sec = 10.0 | |
| 27 | + | network_process_memory_conservative_threshold = 0.6 | |
| 28 | + | network_process_memory_strict_threshold = 0.85 | |
| 29 | + | ||
| 30 | + | [privacy] | |
| 31 | + | cookie_policy = "accept_all" | |
| 32 | + | itp_enabled = true | |
| 33 | + | ||
| 34 | + | [rendering] | |
| 35 | + | mode = "auto" | |
| 36 | + | disable_dmabuf_renderer = false | |
| 37 | + | force_compositing_mode = true | |
| 38 | + | disable_compositing_mode = false | |
| 39 | + | gsk_renderer = "gl" | |
| 40 | + | disable_mipmaps = false | |
| 41 | + | prefer_gl = true | |
| 42 | + | draw_compositing_indicators = false | |
| 43 | + | show_fps = false | |
| 44 | + | sample_memory = false | |
| 45 | + | debug_frames = false | |
| 46 | + | ||
| 47 | + | [media] | |
| 48 | + | hardware_decoding = true | |
| 49 | + | prefer_av1 = false | |
| 50 | + | show_diagnostics = false | |
| 51 | + | force_vsync = true | |
| 52 | + | gl_rendering_mode = "auto" | |
| 53 | + | gstreamer_debug_level = 2 | |
| 54 | + | ||
| 55 | + | [runtime] | |
| 56 | + | prefix = "/usr/local" | |
| 57 | + | ` | |
| 58 | + | ||
| 59 | + | func 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 | + | // engine.type defaults to "webkit" | |
| 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 | |
| 93 | + | assert.Equal(t, "accept_all", 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 | + | ||
| 148 | + | func TestMigrateToEngineConfig_AlreadyMigrated(t *testing.T) { | |
| 149 | + | tmpDir := t.TempDir() | |
| 150 | + | configFile := filepath.Join(tmpDir, "config.toml") | |
| 151 | + | ||
| 152 | + | alreadyMigrated := ` | |
| 153 | + | [engine] | |
| 154 | + | type = "webkit" | |
| 155 | + | profile = "balanced" | |
| 156 | + | ||
| 157 | + | [engine.webkit] | |
| 158 | + | itp_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 | + | ||
| 173 | + | func TestMigrateToEngineConfig_NoOldSections(t *testing.T) { | |
| 174 | + | tmpDir := t.TempDir() | |
| 175 | + | configFile := filepath.Join(tmpDir, "config.toml") | |
| 176 | + | ||
| 177 | + | noOldSections := ` | |
| 178 | + | [history] | |
| 179 | + | max_entries = 1000 | |
| 180 | + | ||
| 181 | + | [logging] | |
| 182 | + | level = "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 | + | ||
| 197 | + | func TestMigrateToEngineConfig_PreservesNonMigratedMediaFields(t *testing.T) { | |
| 198 | + | tmpDir := t.TempDir() | |
| 199 | + | configFile := filepath.Join(tmpDir, "config.toml") | |
| 200 | + | ||
| 201 | + | content := ` | |
| 202 | + | [media] | |
| 203 | + | hardware_decoding = true | |
| 204 | + | prefer_av1 = false | |
| 205 | + | show_diagnostics = true | |
| 206 | + | force_vsync = true | |
| 207 | + | gl_rendering_mode = "auto" | |
| 208 | + | gstreamer_debug_level = 3 | |
| 209 | + | ||
| 210 | + | [rendering] | |
| 211 | + | disable_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 | + | engine := raw["engine"].(map[string]any) | |
| 244 | + | webkit := engine["webkit"].(map[string]any) | |
| 245 | + | assert.Equal(t, true, webkit["force_vsync"]) | |
| 246 | + | assert.Equal(t, "auto", webkit["gl_rendering_mode"]) | |
| 247 | + | assert.Equal(t, int64(3), webkit["gstreamer_debug_level"]) | |
| 248 | + | } | |
| 249 | + | ||
| 250 | + | func TestMigrateToEngineConfig_EngineTypeDefault(t *testing.T) { | |
| 251 | + | tmpDir := t.TempDir() | |
| 252 | + | configFile := filepath.Join(tmpDir, "config.toml") | |
| 253 | + | ||
| 254 | + | // Minimal old-format config to trigger migration | |
| 255 | + | content := ` | |
| 256 | + | [rendering] | |
| 257 | + | show_fps = true | |
| 258 | + | ` | |
| 259 | + | err := os.WriteFile(configFile, []byte(content), 0o644) | |
| 260 | + | require.NoError(t, err) | |
| 261 | + | ||
| 262 | + | migrated, err := MigrateToEngineConfig(configFile) | |
| 263 | + | require.NoError(t, err) | |
| 264 | + | assert.True(t, migrated) | |
| 265 | + | ||
| 266 | + | data, err := os.ReadFile(configFile) | |
| 267 | + | require.NoError(t, err) | |
| 268 | + | ||
| 269 | + | var raw map[string]any | |
| 270 | + | err = toml.Unmarshal(data, &raw) | |
| 271 | + | require.NoError(t, err) | |
| 272 | + | ||
| 273 | + | engine := raw["engine"].(map[string]any) | |
| 274 | + | assert.Equal(t, "webkit", engine["type"], "engine.type should default to webkit") | |
| 275 | + | } | |
| @@ -0,0 +1,275 @@ | |||||
| 1 | + | package config | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 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 | + | ||||
| 13 | + | const oldFormatConfig = ` | |||
| 14 | + | [performance] | |||
| 15 | + | profile = "balanced" | |||
| 16 | + | zoom_cache_size = 50 | |||
| 17 | + | webview_pool_prewarm_count = 2 | |||
| 18 | + | skia_cpu_painting_threads = 4 | |||
| 19 | + | skia_gpu_painting_threads = 2 | |||
| 20 | + | skia_enable_cpu_rendering = false | |||
| 21 | + | web_process_memory_limit_mb = 512 | |||
| 22 | + | web_process_memory_poll_interval_sec = 5.0 | |||
| 23 | + | web_process_memory_conservative_threshold = 0.7 | |||
| 24 | + | web_process_memory_strict_threshold = 0.9 | |||
| 25 | + | network_process_memory_limit_mb = 256 | |||
| 26 | + | network_process_memory_poll_interval_sec = 10.0 | |||
| 27 | + | network_process_memory_conservative_threshold = 0.6 | |||
| 28 | + | network_process_memory_strict_threshold = 0.85 | |||
| 29 | + | ||||
| 30 | + | [privacy] | |||
| 31 | + | cookie_policy = "accept_all" | |||
| 32 | + | itp_enabled = true | |||
| 33 | + | ||||
| 34 | + | [rendering] | |||
| 35 | + | mode = "auto" | |||
| 36 | + | disable_dmabuf_renderer = false | |||
| 37 | + | force_compositing_mode = true | |||
| 38 | + | disable_compositing_mode = false | |||
| 39 | + | gsk_renderer = "gl" | |||
| 40 | + | disable_mipmaps = false | |||
| 41 | + | prefer_gl = true | |||
| 42 | + | draw_compositing_indicators = false | |||
| 43 | + | show_fps = false | |||
| 44 | + | sample_memory = false | |||
| 45 | + | debug_frames = false | |||
| 46 | + | ||||
| 47 | + | [media] | |||
| 48 | + | hardware_decoding = true | |||
| 49 | + | prefer_av1 = false | |||
| 50 | + | show_diagnostics = false | |||
| 51 | + | force_vsync = true | |||
| 52 | + | gl_rendering_mode = "auto" | |||
| 53 | + | gstreamer_debug_level = 2 | |||
| 54 | + | ||||
| 55 | + | [runtime] | |||
| 56 | + | prefix = "/usr/local" | |||
| 57 | + | ` | |||
| 58 | + | ||||
| 59 | + | func 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 | + | // engine.type defaults to "webkit" | |||
| 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 | |||
| 93 | + | assert.Equal(t, "accept_all", 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 | + | ||||
| 148 | + | func TestMigrateToEngineConfig_AlreadyMigrated(t *testing.T) { | |||
| 149 | + | tmpDir := t.TempDir() | |||
| 150 | + | configFile := filepath.Join(tmpDir, "config.toml") | |||
| 151 | + | ||||
| 152 | + | alreadyMigrated := ` | |||
| 153 | + | [engine] | |||
| 154 | + | type = "webkit" | |||
| 155 | + | profile = "balanced" | |||
| 156 | + | ||||
| 157 | + | [engine.webkit] | |||
| 158 | + | itp_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 | + | ||||
| 173 | + | func TestMigrateToEngineConfig_NoOldSections(t *testing.T) { | |||
| 174 | + | tmpDir := t.TempDir() | |||
| 175 | + | configFile := filepath.Join(tmpDir, "config.toml") | |||
| 176 | + | ||||
| 177 | + | noOldSections := ` | |||
| 178 | + | [history] | |||
| 179 | + | max_entries = 1000 | |||
| 180 | + | ||||
| 181 | + | [logging] | |||
| 182 | + | level = "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 | + | ||||
| 197 | + | func TestMigrateToEngineConfig_PreservesNonMigratedMediaFields(t *testing.T) { | |||
| 198 | + | tmpDir := t.TempDir() | |||
| 199 | + | configFile := filepath.Join(tmpDir, "config.toml") | |||
| 200 | + | ||||
| 201 | + | content := ` | |||
| 202 | + | [media] | |||
| 203 | + | hardware_decoding = true | |||
| 204 | + | prefer_av1 = false | |||
| 205 | + | show_diagnostics = true | |||
| 206 | + | force_vsync = true | |||
| 207 | + | gl_rendering_mode = "auto" | |||
| 208 | + | gstreamer_debug_level = 3 | |||
| 209 | + | ||||
| 210 | + | [rendering] | |||
| 211 | + | disable_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 | + | engine := raw["engine"].(map[string]any) | |||
| 244 | + | webkit := engine["webkit"].(map[string]any) | |||
| 245 | + | assert.Equal(t, true, webkit["force_vsync"]) | |||
| 246 | + | assert.Equal(t, "auto", webkit["gl_rendering_mode"]) | |||
| 247 | + | assert.Equal(t, int64(3), webkit["gstreamer_debug_level"]) | |||
| 248 | + | } | |||
| 249 | + | ||||
| 250 | + | func TestMigrateToEngineConfig_EngineTypeDefault(t *testing.T) { | |||
| 251 | + | tmpDir := t.TempDir() | |||
| 252 | + | configFile := filepath.Join(tmpDir, "config.toml") | |||
| 253 | + | ||||
| 254 | + | // Minimal old-format config to trigger migration | |||
| 255 | + | content := ` | |||
| 256 | + | [rendering] | |||
| 257 | + | show_fps = true | |||
| 258 | + | ` | |||
| 259 | + | err := os.WriteFile(configFile, []byte(content), 0o644) | |||
| 260 | + | require.NoError(t, err) | |||
| 261 | + | ||||
| 262 | + | migrated, err := MigrateToEngineConfig(configFile) | |||
| 263 | + | require.NoError(t, err) | |||
| 264 | + | assert.True(t, migrated) | |||
| 265 | + | ||||
| 266 | + | data, err := os.ReadFile(configFile) | |||
| 267 | + | require.NoError(t, err) | |||
| 268 | + | ||||
| 269 | + | var raw map[string]any | |||
| 270 | + | err = toml.Unmarshal(data, &raw) | |||
| 271 | + | require.NoError(t, err) | |||
| 272 | + | ||||
| 273 | + | engine := raw["engine"].(map[string]any) | |||
| 274 | + | assert.Equal(t, "webkit", engine["type"], "engine.type should default to webkit") | |||
| 275 | + | } | |||