refactor(cef): honor engine options and state root
- commit
- d702cff6800ad0260a918afad0421c633f4493bc
- author
- brice · · 2026-04-11 12:46 UTC
- parent
- 5222707
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +125 -23
M internal/bootstrap/engine.go +21 -2
| @@ -42,9 +42,28 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||
| 42 | 42 | input.ThemeManager, input.ColorResolver, input.Logger, | |
| 43 | 43 | ) | |
| 44 | 44 | case config.EngineTypeCEF: | |
| 45 | - | cefCfg := cfg.Engine.CEF | |
| 45 | + | opts := port.EngineOptions{ | |
| 46 | + | DataDir: input.DataDir, | |
| 47 | + | CacheDir: input.CacheDir, | |
| 48 | + | CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy), | |
| 49 | + | } | |
| 50 | + | cefCfg := cef.RuntimeConfig{ | |
| 51 | + | CEFDir: cfg.Engine.CEF.CEFDir, | |
| 52 | + | LogFile: cfg.Engine.CEF.LogFile, | |
| 53 | + | LogSeverity: cfg.Engine.CEF.LogSeverity, | |
| 54 | + | WindowlessFrameRate: cfg.Engine.CEF.WindowlessFrameRate, | |
| 55 | + | EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler, | |
| 56 | + | EnableContextMenuHandler: cfg.Engine.CEF.EnableContextMenuHandler, | |
| 57 | + | TraceHandlers: cfg.Engine.CEF.TraceHandlers, | |
| 58 | + | } | |
| 59 | + | transcodingCfg := cef.TranscodingRuntimeConfig{ | |
| 60 | + | Enabled: cfg.Transcoding.Enabled, | |
| 61 | + | HWAccel: cfg.Transcoding.HWAccel, | |
| 62 | + | MaxConcurrent: cfg.Transcoding.MaxConcurrent, | |
| 63 | + | Quality: cfg.Transcoding.Quality, | |
| 64 | + | } | |
| 46 | 65 | audioFactory := audiofactory.NewAudioOutputFactory() | |
| 47 | - | return cef.NewEngine(input.Ctx, cefCfg, cfg.Transcoding, audioFactory) | |
| 66 | + | return cef.NewEngine(input.Ctx, opts, cefCfg, transcodingCfg, audioFactory, cef.EngineDependencies{}) | |
| 48 | 67 | default: | |
| 49 | 68 | return nil, fmt.Errorf("unknown engine type: %q", engineType) | |
| 50 | 69 | } | |
| @@ -42,9 +42,28 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||||
| 42 | input.ThemeManager, input.ColorResolver, input.Logger, | 42 | input.ThemeManager, input.ColorResolver, input.Logger, | ||
| 43 | ) | 43 | ) | ||
| 44 | case config.EngineTypeCEF: | 44 | case config.EngineTypeCEF: | ||
| 45 | - | cefCfg := cfg.Engine.CEF | 45 | + | opts := port.EngineOptions{ |
| 46 | + | DataDir: input.DataDir, | |||
| 47 | + | CacheDir: input.CacheDir, | |||
| 48 | + | CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy), | |||
| 49 | + | } | |||
| 50 | + | cefCfg := cef.RuntimeConfig{ | |||
| 51 | + | CEFDir: cfg.Engine.CEF.CEFDir, | |||
| 52 | + | LogFile: cfg.Engine.CEF.LogFile, | |||
| 53 | + | LogSeverity: cfg.Engine.CEF.LogSeverity, | |||
| 54 | + | WindowlessFrameRate: cfg.Engine.CEF.WindowlessFrameRate, | |||
| 55 | + | EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler, | |||
| 56 | + | EnableContextMenuHandler: cfg.Engine.CEF.EnableContextMenuHandler, | |||
| 57 | + | TraceHandlers: cfg.Engine.CEF.TraceHandlers, | |||
| 58 | + | } | |||
| 59 | + | transcodingCfg := cef.TranscodingRuntimeConfig{ | |||
| 60 | + | Enabled: cfg.Transcoding.Enabled, | |||
| 61 | + | HWAccel: cfg.Transcoding.HWAccel, | |||
| 62 | + | MaxConcurrent: cfg.Transcoding.MaxConcurrent, | |||
| 63 | + | Quality: cfg.Transcoding.Quality, | |||
| 64 | + | } | |||
| 46 | audioFactory := audiofactory.NewAudioOutputFactory() | 65 | audioFactory := audiofactory.NewAudioOutputFactory() | ||
| 47 | - | return cef.NewEngine(input.Ctx, cefCfg, cfg.Transcoding, audioFactory) | 66 | + | return cef.NewEngine(input.Ctx, opts, cefCfg, transcodingCfg, audioFactory, cef.EngineDependencies{}) |
| 48 | default: | 67 | default: | ||
| 49 | return nil, fmt.Errorf("unknown engine type: %q", engineType) | 68 | return nil, fmt.Errorf("unknown engine type: %q", engineType) | ||
| 50 | } | 69 | } | ||
M internal/bootstrap/engine_test.go +9 -0
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | 4 | "context" | |
| 5 | 5 | "testing" | |
| 6 | 6 | ||
| 7 | + | "github.com/bnema/dumber/internal/infrastructure/cef" | |
| 7 | 8 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 8 | 9 | "github.com/stretchr/testify/require" | |
| 9 | 10 | ) | |
| @@ -24,3 +25,11 @@ func TestBuildEngine_CEF_ReturnsErrorWhenRuntimeUnavailable(t *testing.T) { | |||
| 24 | 25 | require.Error(t, err) | |
| 25 | 26 | require.Contains(t, err.Error(), "cef.InitWithApp") | |
| 26 | 27 | } | |
| 28 | + | ||
| 29 | + | func TestBuildEngine_CEF_ReturnsErrorForUnsupportedCookiePolicy(t *testing.T) { | |
| 30 | + | cfg := config.DefaultConfig() | |
| 31 | + | cfg.Engine.Type = "cef" | |
| 32 | + | cfg.Engine.CookiePolicy = config.CookiePolicyNever | |
| 33 | + | _, err := BuildEngine(EngineInput{Config: cfg, Ctx: context.Background()}) | |
| 34 | + | require.ErrorIs(t, err, cef.ErrCookiePolicyUnsupported) | |
| 35 | + | } | |
| @@ -4,6 +4,7 @@ import ( | |||||
| 4 | "context" | 4 | "context" | ||
| 5 | "testing" | 5 | "testing" | ||
| 6 | 6 | ||||
| 7 | + | "github.com/bnema/dumber/internal/infrastructure/cef" | |||
| 7 | "github.com/bnema/dumber/internal/infrastructure/config" | 8 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 8 | "github.com/stretchr/testify/require" | 9 | "github.com/stretchr/testify/require" | ||
| 9 | ) | 10 | ) | ||
| @@ -24,3 +25,11 @@ func TestBuildEngine_CEF_ReturnsErrorWhenRuntimeUnavailable(t *testing.T) { | |||||
| 24 | require.Error(t, err) | 25 | require.Error(t, err) | ||
| 25 | require.Contains(t, err.Error(), "cef.InitWithApp") | 26 | require.Contains(t, err.Error(), "cef.InitWithApp") | ||
| 26 | } | 27 | } | ||
| 28 | + | ||||
| 29 | + | func TestBuildEngine_CEF_ReturnsErrorForUnsupportedCookiePolicy(t *testing.T) { | |||
| 30 | + | cfg := config.DefaultConfig() | |||
| 31 | + | cfg.Engine.Type = "cef" | |||
| 32 | + | cfg.Engine.CookiePolicy = config.CookiePolicyNever | |||
| 33 | + | _, err := BuildEngine(EngineInput{Config: cfg, Ctx: context.Background()}) | |||
| 34 | + | require.ErrorIs(t, err, cef.ErrCookiePolicyUnsupported) | |||
| 35 | + | } | |||
M internal/infrastructure/cef/engine_init.go +67 -19
| @@ -26,16 +26,37 @@ const puregoCEFInitTraceEnvVar = "PUREGO_CEF_INIT_TRACE" | |||
| 26 | 26 | // NewEngine initializes the CEF runtime and returns a ready-to-use Engine. | |
| 27 | 27 | func NewEngine( | |
| 28 | 28 | ctx context.Context, | |
| 29 | - | cfg config.CEFEngineConfig, | |
| 30 | - | transcodingCfg config.TranscodingConfig, | |
| 29 | + | opts port.EngineOptions, | |
| 30 | + | cfg RuntimeConfig, | |
| 31 | + | transcodingCfg TranscodingRuntimeConfig, | |
| 31 | 32 | audioFactory port.AudioOutputFactory, | |
| 33 | + | deps EngineDependencies, | |
| 32 | 34 | ) (*Engine, error) { | |
| 33 | 35 | logger := logging.FromContext(ctx) | |
| 34 | - | cleanStaleSingletonLocks(logger) | |
| 35 | - | ||
| 36 | - | windowlessFrameRate := cfg.CEFWindowlessFrameRate() | |
| 37 | - | ||
| 38 | - | settings := prepareCEFSettings(cfg, logger) | |
| 36 | + | stateRoot := resolvedStateRoot(opts) | |
| 37 | + | cleanStaleSingletonLocks(logger, stateRoot) | |
| 38 | + | ||
| 39 | + | legacyCfg := config.CEFEngineConfig{ | |
| 40 | + | CEFDir: cfg.CEFDir, | |
| 41 | + | LogFile: cfg.LogFile, | |
| 42 | + | LogSeverity: cfg.LogSeverity, | |
| 43 | + | WindowlessFrameRate: cfg.WindowlessFrameRate, | |
| 44 | + | EnableAudioHandler: cfg.EnableAudioHandler, | |
| 45 | + | EnableContextMenuHandler: cfg.EnableContextMenuHandler, | |
| 46 | + | TraceHandlers: cfg.TraceHandlers, | |
| 47 | + | } | |
| 48 | + | legacyTranscodingCfg := config.TranscodingConfig{ | |
| 49 | + | Enabled: transcodingCfg.Enabled, | |
| 50 | + | HWAccel: transcodingCfg.HWAccel, | |
| 51 | + | MaxConcurrent: transcodingCfg.MaxConcurrent, | |
| 52 | + | Quality: transcodingCfg.Quality, | |
| 53 | + | } | |
| 54 | + | windowlessFrameRate := legacyCfg.CEFWindowlessFrameRate() | |
| 55 | + | ||
| 56 | + | settings, err := prepareCEFSettings(opts, cfg, logger) | |
| 57 | + | if err != nil { | |
| 58 | + | return nil, err | |
| 59 | + | } | |
| 39 | 60 | ||
| 40 | 61 | // Inject --no-zygote temporarily for cef_initialize, then restore. | |
| 41 | 62 | // Safe: runs during single-threaded startup before concurrent goroutines. | |
| @@ -60,24 +81,49 @@ func NewEngine( | |||
| 60 | 81 | } | |
| 61 | 82 | os.Args = savedArgs | |
| 62 | 83 | ||
| 63 | - | return wireEngine(ctx, eng, cfg, transcodingCfg, windowlessFrameRate, audioFactory, logger) | |
| 84 | + | return wireEngine(ctx, eng, legacyCfg, legacyTranscodingCfg, windowlessFrameRate, audioFactory, logger) | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | func resolvedStateRoot(opts port.EngineOptions) string { | |
| 88 | + | switch { | |
| 89 | + | case opts.DataDir != "": | |
| 90 | + | return opts.DataDir | |
| 91 | + | case opts.CacheDir != "": | |
| 92 | + | return opts.CacheDir | |
| 93 | + | default: | |
| 94 | + | return defaultCEFUserDataDir() | |
| 95 | + | } | |
| 64 | 96 | } | |
| 65 | 97 | ||
| 66 | 98 | // prepareCEFSettings builds purecef.Settings from the engine config. | |
| 67 | - | func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) purecef.Settings { | |
| 99 | + | func prepareCEFSettings(opts port.EngineOptions, cfg RuntimeConfig, logger *zerolog.Logger) (purecef.Settings, error) { | |
| 100 | + | if opts.CookiePolicy != "" && opts.CookiePolicy != port.CookiePolicyAlways { | |
| 101 | + | return purecef.Settings{}, fmt.Errorf("%w: %s", ErrCookiePolicyUnsupported, opts.CookiePolicy) | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | legacyCfg := config.CEFEngineConfig{ | |
| 105 | + | CEFDir: cfg.CEFDir, | |
| 106 | + | LogFile: cfg.LogFile, | |
| 107 | + | LogSeverity: cfg.LogSeverity, | |
| 108 | + | WindowlessFrameRate: cfg.WindowlessFrameRate, | |
| 109 | + | EnableAudioHandler: cfg.EnableAudioHandler, | |
| 110 | + | EnableContextMenuHandler: cfg.EnableContextMenuHandler, | |
| 111 | + | TraceHandlers: cfg.TraceHandlers, | |
| 112 | + | } | |
| 113 | + | ||
| 68 | 114 | settings := purecef.DefaultSettings() | |
| 69 | 115 | settings.MultiThreadedMessageLoop = true | |
| 70 | 116 | settings.ExternalMessagePump = false | |
| 71 | - | settings.RootCachePath = defaultCEFUserDataDir() | |
| 72 | - | if cfg.CEFDir != "" { | |
| 73 | - | settings.CEFDir = cfg.CEFDir | |
| 117 | + | settings.RootCachePath = resolvedStateRoot(opts) | |
| 118 | + | if legacyCfg.CEFDir != "" { | |
| 119 | + | settings.CEFDir = legacyCfg.CEFDir | |
| 74 | 120 | } | |
| 75 | - | if runtimeLogFile, err := prepareCEFLogFile(cfg); err != nil { | |
| 121 | + | if runtimeLogFile, err := prepareCEFLogFile(legacyCfg); err != nil { | |
| 76 | 122 | logger.Warn().Err(err).Msg("cef: failed to prepare runtime log file") | |
| 77 | 123 | } else { | |
| 78 | 124 | settings.LogFile = runtimeLogFile | |
| 79 | 125 | } | |
| 80 | - | if bootstrapLogFile, err := prepareCEFInitTraceFile(cfg); err != nil { | |
| 126 | + | if bootstrapLogFile, err := prepareCEFInitTraceFile(legacyCfg); err != nil { | |
| 81 | 127 | logger.Warn().Err(err).Msg("cef: failed to prepare init trace file") | |
| 82 | 128 | } else if bootstrapLogFile != "" { | |
| 83 | 129 | settings.InitTraceFile = bootstrapLogFile | |
| @@ -86,8 +132,8 @@ func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) pure | |||
| 86 | 132 | Str("env_var", puregoCEFInitTraceEnvVar). | |
| 87 | 133 | Msg("cef: init bootstrap diagnostics enabled") | |
| 88 | 134 | } | |
| 89 | - | if cfg.LogSeverity != 0 { | |
| 90 | - | settings.LogSeverity = cfg.LogSeverity | |
| 135 | + | if legacyCfg.LogSeverity != 0 { | |
| 136 | + | settings.LogSeverity = legacyCfg.LogSeverity | |
| 91 | 137 | } | |
| 92 | 138 | if helperPath := findHelperBinary(); helperPath != "" { | |
| 93 | 139 | settings.BrowserSubprocessPath = helperPath | |
| @@ -95,7 +141,7 @@ func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) pure | |||
| 95 | 141 | } else { | |
| 96 | 142 | logger.Warn().Msg("cef: subprocess helper not found, falling back to main binary") | |
| 97 | 143 | } | |
| 98 | - | return settings | |
| 144 | + | return settings, nil | |
| 99 | 145 | } | |
| 100 | 146 | ||
| 101 | 147 | func defaultCEFUserDataDir() string { | |
| @@ -302,8 +348,10 @@ func appendIfMissing(args []string, flag string) []string { | |||
| 302 | 348 | // if the owning process is no longer running. CEF leaves these behind on | |
| 303 | 349 | // unclean shutdown (SIGKILL, SIGSEGV) and the next instance crashes trying | |
| 304 | 350 | // to connect to the dead process. | |
| 305 | - | func cleanStaleSingletonLocks(logger *zerolog.Logger) { | |
| 306 | - | dir := defaultCEFUserDataDir() | |
| 351 | + | func cleanStaleSingletonLocks(logger *zerolog.Logger, dir string) { | |
| 352 | + | if dir == "" { | |
| 353 | + | return | |
| 354 | + | } | |
| 307 | 355 | ||
| 308 | 356 | lockPath := filepath.Join(dir, "SingletonLock") | |
| 309 | 357 | target, err := os.Readlink(lockPath) | |
| @@ -26,16 +26,37 @@ const puregoCEFInitTraceEnvVar = "PUREGO_CEF_INIT_TRACE" | |||||
| 26 | // NewEngine initializes the CEF runtime and returns a ready-to-use Engine. | 26 | // NewEngine initializes the CEF runtime and returns a ready-to-use Engine. | ||
| 27 | func NewEngine( | 27 | func NewEngine( | ||
| 28 | ctx context.Context, | 28 | ctx context.Context, | ||
| 29 | - | cfg config.CEFEngineConfig, | 29 | + | opts port.EngineOptions, |
| 30 | - | transcodingCfg config.TranscodingConfig, | 30 | + | cfg RuntimeConfig, |
| 31 | + | transcodingCfg TranscodingRuntimeConfig, | |||
| 31 | audioFactory port.AudioOutputFactory, | 32 | audioFactory port.AudioOutputFactory, | ||
| 33 | + | deps EngineDependencies, | |||
| 32 | ) (*Engine, error) { | 34 | ) (*Engine, error) { | ||
| 33 | logger := logging.FromContext(ctx) | 35 | logger := logging.FromContext(ctx) | ||
| 34 | - | cleanStaleSingletonLocks(logger) | 36 | + | stateRoot := resolvedStateRoot(opts) |
| 35 | - | 37 | + | cleanStaleSingletonLocks(logger, stateRoot) | |
| 36 | - | windowlessFrameRate := cfg.CEFWindowlessFrameRate() | 38 | + | |
| 37 | - | 39 | + | legacyCfg := config.CEFEngineConfig{ | |
| 38 | - | settings := prepareCEFSettings(cfg, logger) | 40 | + | CEFDir: cfg.CEFDir, |
| 41 | + | LogFile: cfg.LogFile, | |||
| 42 | + | LogSeverity: cfg.LogSeverity, | |||
| 43 | + | WindowlessFrameRate: cfg.WindowlessFrameRate, | |||
| 44 | + | EnableAudioHandler: cfg.EnableAudioHandler, | |||
| 45 | + | EnableContextMenuHandler: cfg.EnableContextMenuHandler, | |||
| 46 | + | TraceHandlers: cfg.TraceHandlers, | |||
| 47 | + | } | |||
| 48 | + | legacyTranscodingCfg := config.TranscodingConfig{ | |||
| 49 | + | Enabled: transcodingCfg.Enabled, | |||
| 50 | + | HWAccel: transcodingCfg.HWAccel, | |||
| 51 | + | MaxConcurrent: transcodingCfg.MaxConcurrent, | |||
| 52 | + | Quality: transcodingCfg.Quality, | |||
| 53 | + | } | |||
| 54 | + | windowlessFrameRate := legacyCfg.CEFWindowlessFrameRate() | |||
| 55 | + | ||||
| 56 | + | settings, err := prepareCEFSettings(opts, cfg, logger) | |||
| 57 | + | if err != nil { | |||
| 58 | + | return nil, err | |||
| 59 | + | } | |||
| 39 | 60 | ||||
| 40 | // Inject --no-zygote temporarily for cef_initialize, then restore. | 61 | // Inject --no-zygote temporarily for cef_initialize, then restore. | ||
| 41 | // Safe: runs during single-threaded startup before concurrent goroutines. | 62 | // Safe: runs during single-threaded startup before concurrent goroutines. | ||
| @@ -60,24 +81,49 @@ func NewEngine( | |||||
| 60 | } | 81 | } | ||
| 61 | os.Args = savedArgs | 82 | os.Args = savedArgs | ||
| 62 | 83 | ||||
| 63 | - | return wireEngine(ctx, eng, cfg, transcodingCfg, windowlessFrameRate, audioFactory, logger) | 84 | + | return wireEngine(ctx, eng, legacyCfg, legacyTranscodingCfg, windowlessFrameRate, audioFactory, logger) |
| 85 | + | } | |||
| 86 | + | ||||
| 87 | + | func resolvedStateRoot(opts port.EngineOptions) string { | |||
| 88 | + | switch { | |||
| 89 | + | case opts.DataDir != "": | |||
| 90 | + | return opts.DataDir | |||
| 91 | + | case opts.CacheDir != "": | |||
| 92 | + | return opts.CacheDir | |||
| 93 | + | default: | |||
| 94 | + | return defaultCEFUserDataDir() | |||
| 95 | + | } | |||
| 64 | } | 96 | } | ||
| 65 | 97 | ||||
| 66 | // prepareCEFSettings builds purecef.Settings from the engine config. | 98 | // prepareCEFSettings builds purecef.Settings from the engine config. | ||
| 67 | - | func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) purecef.Settings { | 99 | + | func prepareCEFSettings(opts port.EngineOptions, cfg RuntimeConfig, logger *zerolog.Logger) (purecef.Settings, error) { |
| 100 | + | if opts.CookiePolicy != "" && opts.CookiePolicy != port.CookiePolicyAlways { | |||
| 101 | + | return purecef.Settings{}, fmt.Errorf("%w: %s", ErrCookiePolicyUnsupported, opts.CookiePolicy) | |||
| 102 | + | } | |||
| 103 | + | ||||
| 104 | + | legacyCfg := config.CEFEngineConfig{ | |||
| 105 | + | CEFDir: cfg.CEFDir, | |||
| 106 | + | LogFile: cfg.LogFile, | |||
| 107 | + | LogSeverity: cfg.LogSeverity, | |||
| 108 | + | WindowlessFrameRate: cfg.WindowlessFrameRate, | |||
| 109 | + | EnableAudioHandler: cfg.EnableAudioHandler, | |||
| 110 | + | EnableContextMenuHandler: cfg.EnableContextMenuHandler, | |||
| 111 | + | TraceHandlers: cfg.TraceHandlers, | |||
| 112 | + | } | |||
| 113 | + | ||||
| 68 | settings := purecef.DefaultSettings() | 114 | settings := purecef.DefaultSettings() | ||
| 69 | settings.MultiThreadedMessageLoop = true | 115 | settings.MultiThreadedMessageLoop = true | ||
| 70 | settings.ExternalMessagePump = false | 116 | settings.ExternalMessagePump = false | ||
| 71 | - | settings.RootCachePath = defaultCEFUserDataDir() | 117 | + | settings.RootCachePath = resolvedStateRoot(opts) |
| 72 | - | if cfg.CEFDir != "" { | 118 | + | if legacyCfg.CEFDir != "" { |
| 73 | - | settings.CEFDir = cfg.CEFDir | 119 | + | settings.CEFDir = legacyCfg.CEFDir |
| 74 | } | 120 | } | ||
| 75 | - | if runtimeLogFile, err := prepareCEFLogFile(cfg); err != nil { | 121 | + | if runtimeLogFile, err := prepareCEFLogFile(legacyCfg); err != nil { |
| 76 | logger.Warn().Err(err).Msg("cef: failed to prepare runtime log file") | 122 | logger.Warn().Err(err).Msg("cef: failed to prepare runtime log file") | ||
| 77 | } else { | 123 | } else { | ||
| 78 | settings.LogFile = runtimeLogFile | 124 | settings.LogFile = runtimeLogFile | ||
| 79 | } | 125 | } | ||
| 80 | - | if bootstrapLogFile, err := prepareCEFInitTraceFile(cfg); err != nil { | 126 | + | if bootstrapLogFile, err := prepareCEFInitTraceFile(legacyCfg); err != nil { |
| 81 | logger.Warn().Err(err).Msg("cef: failed to prepare init trace file") | 127 | logger.Warn().Err(err).Msg("cef: failed to prepare init trace file") | ||
| 82 | } else if bootstrapLogFile != "" { | 128 | } else if bootstrapLogFile != "" { | ||
| 83 | settings.InitTraceFile = bootstrapLogFile | 129 | settings.InitTraceFile = bootstrapLogFile | ||
| @@ -86,8 +132,8 @@ func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) pure | |||||
| 86 | Str("env_var", puregoCEFInitTraceEnvVar). | 132 | Str("env_var", puregoCEFInitTraceEnvVar). | ||
| 87 | Msg("cef: init bootstrap diagnostics enabled") | 133 | Msg("cef: init bootstrap diagnostics enabled") | ||
| 88 | } | 134 | } | ||
| 89 | - | if cfg.LogSeverity != 0 { | 135 | + | if legacyCfg.LogSeverity != 0 { |
| 90 | - | settings.LogSeverity = cfg.LogSeverity | 136 | + | settings.LogSeverity = legacyCfg.LogSeverity |
| 91 | } | 137 | } | ||
| 92 | if helperPath := findHelperBinary(); helperPath != "" { | 138 | if helperPath := findHelperBinary(); helperPath != "" { | ||
| 93 | settings.BrowserSubprocessPath = helperPath | 139 | settings.BrowserSubprocessPath = helperPath | ||
| @@ -95,7 +141,7 @@ func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) pure | |||||
| 95 | } else { | 141 | } else { | ||
| 96 | logger.Warn().Msg("cef: subprocess helper not found, falling back to main binary") | 142 | logger.Warn().Msg("cef: subprocess helper not found, falling back to main binary") | ||
| 97 | } | 143 | } | ||
| 98 | - | return settings | 144 | + | return settings, nil |
| 99 | } | 145 | } | ||
| 100 | 146 | ||||
| 101 | func defaultCEFUserDataDir() string { | 147 | func defaultCEFUserDataDir() string { | ||
| @@ -302,8 +348,10 @@ func appendIfMissing(args []string, flag string) []string { | |||||
| 302 | // if the owning process is no longer running. CEF leaves these behind on | 348 | // if the owning process is no longer running. CEF leaves these behind on | ||
| 303 | // unclean shutdown (SIGKILL, SIGSEGV) and the next instance crashes trying | 349 | // unclean shutdown (SIGKILL, SIGSEGV) and the next instance crashes trying | ||
| 304 | // to connect to the dead process. | 350 | // to connect to the dead process. | ||
| 305 | - | func cleanStaleSingletonLocks(logger *zerolog.Logger) { | 351 | + | func cleanStaleSingletonLocks(logger *zerolog.Logger, dir string) { |
| 306 | - | dir := defaultCEFUserDataDir() | 352 | + | if dir == "" { |
| 353 | + | return | |||
| 354 | + | } | |||
| 307 | 355 | ||||
| 308 | lockPath := filepath.Join(dir, "SingletonLock") | 356 | lockPath := filepath.Join(dir, "SingletonLock") | ||
| 309 | target, err := os.Readlink(lockPath) | 357 | target, err := os.Readlink(lockPath) | ||
M internal/infrastructure/cef/engine_init_test.go +28 -2
| @@ -5,12 +5,36 @@ import ( | |||
| 5 | 5 | "path/filepath" | |
| 6 | 6 | "testing" | |
| 7 | 7 | ||
| 8 | + | "github.com/bnema/dumber/internal/application/port" | |
| 8 | 9 | "github.com/bnema/dumber/internal/infrastructure/audio/factory" | |
| 9 | 10 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 10 | 11 | "github.com/rs/zerolog" | |
| 11 | 12 | "github.com/stretchr/testify/require" | |
| 12 | 13 | ) | |
| 13 | 14 | ||
| 15 | + | func TestResolvedStateRoot_PrefersDataDir(t *testing.T) { | |
| 16 | + | root := resolvedStateRoot(port.EngineOptions{DataDir: "/tmp/data", CacheDir: "/tmp/cache"}) | |
| 17 | + | require.Equal(t, "/tmp/data", root) | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | func TestResolvedStateRoot_FallsBackToCacheDir(t *testing.T) { | |
| 21 | + | root := resolvedStateRoot(port.EngineOptions{CacheDir: "/tmp/cache"}) | |
| 22 | + | require.Equal(t, "/tmp/cache", root) | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | func TestPrepareCEFSettings_UsesResolvedStateRoot(t *testing.T) { | |
| 26 | + | logger := zerolog.Nop() | |
| 27 | + | settings, err := prepareCEFSettings(port.EngineOptions{CacheDir: "/tmp/cache"}, RuntimeConfig{}, &logger) | |
| 28 | + | require.NoError(t, err) | |
| 29 | + | require.Equal(t, "/tmp/cache", settings.RootCachePath) | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | func TestPrepareCEFSettings_RejectsNonDefaultCookiePolicy(t *testing.T) { | |
| 33 | + | logger := zerolog.Nop() | |
| 34 | + | _, err := prepareCEFSettings(port.EngineOptions{CookiePolicy: port.CookiePolicyNever}, RuntimeConfig{}, &logger) | |
| 35 | + | require.ErrorIs(t, err, ErrCookiePolicyUnsupported) | |
| 36 | + | } | |
| 37 | + | ||
| 14 | 38 | func TestPrepareCEFInitTraceFile_DisabledByDefault(t *testing.T) { | |
| 15 | 39 | t.Setenv(puregoCEFInitTraceEnvVar, "") | |
| 16 | 40 | ||
| @@ -37,7 +61,8 @@ func TestPrepareCEFSettings_RootCachePath_UsesLegacyCEFUserDataDir(t *testing.T) | |||
| 37 | 61 | t.Setenv("ENV", "") | |
| 38 | 62 | ||
| 39 | 63 | logger := zerolog.Nop() | |
| 40 | - | settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger) | |
| 64 | + | settings, err := prepareCEFSettings(port.EngineOptions{}, RuntimeConfig{}, &logger) | |
| 65 | + | require.NoError(t, err) | |
| 41 | 66 | ||
| 42 | 67 | require.Equal(t, filepath.Join(homeDir, ".config", "cef_user_data"), settings.RootCachePath) | |
| 43 | 68 | } | |
| @@ -58,7 +83,8 @@ func TestPrepareCEFSettings_RootCachePath_DevMode(t *testing.T) { | |||
| 58 | 83 | t.Setenv("ENV", "dev") | |
| 59 | 84 | ||
| 60 | 85 | logger := zerolog.Nop() | |
| 61 | - | settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger) | |
| 86 | + | settings, err := prepareCEFSettings(port.EngineOptions{}, RuntimeConfig{}, &logger) | |
| 87 | + | require.NoError(t, err) | |
| 62 | 88 | ||
| 63 | 89 | // In dev mode, should resolve under .dev/dumber/cef_user_data | |
| 64 | 90 | require.Equal(t, filepath.Join(tempDir, ".dev", "dumber", "cef_user_data"), settings.RootCachePath) | |
| @@ -5,12 +5,36 @@ import ( | |||||
| 5 | "path/filepath" | 5 | "path/filepath" | ||
| 6 | "testing" | 6 | "testing" | ||
| 7 | 7 | ||||
| 8 | + | "github.com/bnema/dumber/internal/application/port" | |||
| 8 | "github.com/bnema/dumber/internal/infrastructure/audio/factory" | 9 | "github.com/bnema/dumber/internal/infrastructure/audio/factory" | ||
| 9 | "github.com/bnema/dumber/internal/infrastructure/config" | 10 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 10 | "github.com/rs/zerolog" | 11 | "github.com/rs/zerolog" | ||
| 11 | "github.com/stretchr/testify/require" | 12 | "github.com/stretchr/testify/require" | ||
| 12 | ) | 13 | ) | ||
| 13 | 14 | ||||
| 15 | + | func TestResolvedStateRoot_PrefersDataDir(t *testing.T) { | |||
| 16 | + | root := resolvedStateRoot(port.EngineOptions{DataDir: "/tmp/data", CacheDir: "/tmp/cache"}) | |||
| 17 | + | require.Equal(t, "/tmp/data", root) | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | func TestResolvedStateRoot_FallsBackToCacheDir(t *testing.T) { | |||
| 21 | + | root := resolvedStateRoot(port.EngineOptions{CacheDir: "/tmp/cache"}) | |||
| 22 | + | require.Equal(t, "/tmp/cache", root) | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | func TestPrepareCEFSettings_UsesResolvedStateRoot(t *testing.T) { | |||
| 26 | + | logger := zerolog.Nop() | |||
| 27 | + | settings, err := prepareCEFSettings(port.EngineOptions{CacheDir: "/tmp/cache"}, RuntimeConfig{}, &logger) | |||
| 28 | + | require.NoError(t, err) | |||
| 29 | + | require.Equal(t, "/tmp/cache", settings.RootCachePath) | |||
| 30 | + | } | |||
| 31 | + | ||||
| 32 | + | func TestPrepareCEFSettings_RejectsNonDefaultCookiePolicy(t *testing.T) { | |||
| 33 | + | logger := zerolog.Nop() | |||
| 34 | + | _, err := prepareCEFSettings(port.EngineOptions{CookiePolicy: port.CookiePolicyNever}, RuntimeConfig{}, &logger) | |||
| 35 | + | require.ErrorIs(t, err, ErrCookiePolicyUnsupported) | |||
| 36 | + | } | |||
| 37 | + | ||||
| 14 | func TestPrepareCEFInitTraceFile_DisabledByDefault(t *testing.T) { | 38 | func TestPrepareCEFInitTraceFile_DisabledByDefault(t *testing.T) { | ||
| 15 | t.Setenv(puregoCEFInitTraceEnvVar, "") | 39 | t.Setenv(puregoCEFInitTraceEnvVar, "") | ||
| 16 | 40 | ||||
| @@ -37,7 +61,8 @@ func TestPrepareCEFSettings_RootCachePath_UsesLegacyCEFUserDataDir(t *testing.T) | |||||
| 37 | t.Setenv("ENV", "") | 61 | t.Setenv("ENV", "") | ||
| 38 | 62 | ||||
| 39 | logger := zerolog.Nop() | 63 | logger := zerolog.Nop() | ||
| 40 | - | settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger) | 64 | + | settings, err := prepareCEFSettings(port.EngineOptions{}, RuntimeConfig{}, &logger) |
| 65 | + | require.NoError(t, err) | |||
| 41 | 66 | ||||
| 42 | require.Equal(t, filepath.Join(homeDir, ".config", "cef_user_data"), settings.RootCachePath) | 67 | require.Equal(t, filepath.Join(homeDir, ".config", "cef_user_data"), settings.RootCachePath) | ||
| 43 | } | 68 | } | ||
| @@ -58,7 +83,8 @@ func TestPrepareCEFSettings_RootCachePath_DevMode(t *testing.T) { | |||||
| 58 | t.Setenv("ENV", "dev") | 83 | t.Setenv("ENV", "dev") | ||
| 59 | 84 | ||||
| 60 | logger := zerolog.Nop() | 85 | logger := zerolog.Nop() | ||
| 61 | - | settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger) | 86 | + | settings, err := prepareCEFSettings(port.EngineOptions{}, RuntimeConfig{}, &logger) |
| 87 | + | require.NoError(t, err) | |||
| 62 | 88 | ||||
| 63 | // In dev mode, should resolve under .dev/dumber/cef_user_data | 89 | // In dev mode, should resolve under .dev/dumber/cef_user_data | ||
| 64 | require.Equal(t, filepath.Join(tempDir, ".dev", "dumber", "cef_user_data"), settings.RootCachePath) | 90 | require.Equal(t, filepath.Join(tempDir, ".dev", "dumber", "cef_user_data"), settings.RootCachePath) | ||