Files
cthulhu/internal/wine/live_settings_test.go
hjozwiak 9a10a15b68 Read Wine settings live at poll and spawn
Phase 2 of the Wine/Proton stage (decisions 3, 4, 16, 17, 19 in docs/testing/wine-proton.md): replace the Wine manager's startup snapshot with a live settings source read once per poll, so accepted global settings take effect with no reader restart. The source publishes only the active/global profile's Wine values; per-application profile switching never changes the governing list. A newly listed running program restarts its helper once in the same mode; removals and clearing the global switch never downgrade a running helper.
2026-11-02 01:15:33 -05:00

104 lines
3.3 KiB
Go

// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package wine
import (
"os"
"path/filepath"
"strings"
"testing"
)
// The manager must read its settings source at each poll and helper spawn,
// never a snapshot taken when it was constructed. A live addition for a
// currently discovered program restarts the running helper once in the same
// mode, and removals never downgrade a running helper.
func TestManagerReadsSettingsSourceAtEachPoll(t *testing.T) {
t.Setenv("WINE_ACCESS_NO_DIALOG_READER", "")
root := t.TempDir()
procRoot := filepath.Join(root, "proc")
loader := filepath.Join(root, "wine")
write_helper_file(t, loader, "loader")
base := filepath.Join(procRoot, "101")
write_helper_file(t, filepath.Join(base, "cmdline"), "launcher.exe\x00")
write_helper_file(t, filepath.Join(base, "environ"), "WINEPREFIX="+root+"\x00")
if err := os.Symlink(loader, filepath.Join(base, "exe")); err != nil {
t.Fatal(err)
}
helper := filepath.Join(root, "helper")
write_helper_file(t, helper, "#!/bin/sh\nexec sleep 30\n")
current := Settings{}
reads := 0
manager := New(nil)
manager.helper, manager.procRoot = helper, procRoot
manager.SettingsSource = func() Settings {
reads++
return current
}
active := map[string]*process{}
defer func() {
for _, child := range active {
stop(child)
}
}()
manager.poll(active)
child := active[root]
if child == nil || child.cmd == nil || child.controllerOnly {
t.Fatal("helper did not start normally")
}
if len(child.exclusions) != 0 {
t.Fatalf("initial exclusions = %v", child.exclusions)
}
if reads == 0 {
t.Fatal("poll did not read the settings source")
}
pid := child.cmd.Process.Pid
// A live addition naming a currently discovered program reaches the
// running helper at the next poll: one restart, same mode, flag added.
readsBefore := reads
current = Settings{ExcludedPrograms: []string{"Launcher.exe"}}
manager.poll(active)
if reads <= readsBefore {
t.Fatal("later poll did not re-read the settings source")
}
if child.controllerOnly || child.cmd == nil || child.cmd.Process.Pid == pid {
t.Fatal("live addition did not restart the helper in the same mode")
}
if !child.exclusions["launcher.exe"] {
t.Fatal("restarted helper lacks the live exclusion flag")
}
if got := strings.Join(child.cmd.Args[1:], " "); got != "--exclude-hook-program launcher.exe" {
t.Fatalf("restarted helper arguments = %q", got)
}
pid = child.cmd.Process.Pid
manager.poll(active)
if child.cmd.Process.Pid != pid {
t.Fatal("helper restarted more than once for one live addition")
}
// Removing the listing never restarts or downgrades the running helper.
current = Settings{}
manager.poll(active)
if child.cmd.Process.Pid != pid {
t.Fatal("helper restarted when a live listing was removed")
}
// Turning the global switch on live upgrades the helper wholesale;
// clearing it live never downgrades the running helper.
current = Settings{ControllerOnly: true}
manager.poll(active)
if !child.controllerOnly || child.cmd == nil || child.cmd.Process.Pid == pid {
t.Fatal("live global switch did not upgrade the helper wholesale")
}
pid = child.cmd.Process.Pid
current = Settings{}
manager.poll(active)
if !child.controllerOnly || child.cmd.Process.Pid != pid {
t.Fatal("wholesale helper downgraded after the live switch was cleared")
}
}