// 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") } }