Add Wine ready-record and mixed-prefix spawn semantics
Phase 1 of the Wine/Proton stage (decisions 2, 5, 16, 17, 18, 24 in docs/testing/wine-proton.md): classify the helper exclusion-count line as info, parse the ready hook_exclusions count, mirror programs=[...] extra=[...] in the helper-start record, and stop passing prefix-wide --no-dialog-reader merely because a listed program is present, so ordinary programs in a mixed prefix keep dialog reading. Wholesale mode remains for the global controller-only switch, the environment override, and helpers whose ready record does not confirm the exclusions sent; unconfirmed binaries are remembered. A newly listed running program restarts its helper once in the same mode; removals never downgrade.
This commit is contained in:
1 parent
6ed0e36960
commit
71093286c7
5 files changed
+459
-55
No files matched your search
@@ -144,6 +144,104 @@ func TestManagerExpectedStopIsNotAHelperFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperDiagnosticsHookExclusionEvidence(t *testing.T) {
|
||||
var log bytes.Buffer
|
||||
output := newHelperDiagnostics()
|
||||
readyRecords := make(chan helperReady, 4)
|
||||
output.onReady = func(ready helperReady) { readyRecords <- ready }
|
||||
go output.logRecords(slog.New(slog.NewJSONHandler(&log, nil)))
|
||||
for _, line := range []string{
|
||||
"wine-access: hook exclusions for 2 program(s)\n",
|
||||
"wine-access: ignoring invalid --exclude-hook-program value\n",
|
||||
"wine-access: ready {\"reader\":\"org.example.Reader\",\"controls_enabled\":true,\"hook_exclusions\":2}\n",
|
||||
// Older helpers have no count field; they report zero.
|
||||
"wine-access: ready {\"reader\":\"org.example.Reader\",\"controls_enabled\":true}\n",
|
||||
} {
|
||||
if _, err := output.Write([]byte(line)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
output.finish()
|
||||
<-output.done
|
||||
levels := map[string]string{}
|
||||
var exclusionCounts []float64
|
||||
for _, line := range strings.Split(strings.TrimSpace(log.String()), "\n") {
|
||||
var record map[string]any
|
||||
if err := json.Unmarshal([]byte(line), &record); err != nil {
|
||||
t.Fatalf("invalid log record %q: %v", line, err)
|
||||
}
|
||||
if diagnostic, ok := record["diagnostic"].(string); ok {
|
||||
levels[diagnostic] = record["level"].(string)
|
||||
}
|
||||
if record["msg"] == "Wine accessibility helper initialized" {
|
||||
count, ok := record["hook_exclusions"].(float64)
|
||||
if !ok {
|
||||
t.Fatalf("initialized record lacks hook_exclusions: %s", line)
|
||||
}
|
||||
exclusionCounts = append(exclusionCounts, count)
|
||||
}
|
||||
}
|
||||
if levels["hook exclusions for 2 program(s)"] != "INFO" {
|
||||
t.Fatalf("plain exclusion-count line levels = %v", levels)
|
||||
}
|
||||
if levels["ignoring invalid --exclude-hook-program value"] != "WARN" {
|
||||
t.Fatalf("invalid exclusion line levels = %v", levels)
|
||||
}
|
||||
if len(exclusionCounts) != 2 || exclusionCounts[0] != 2 || exclusionCounts[1] != 0 {
|
||||
t.Fatalf("logged hook_exclusions = %v, want [2 0]", exclusionCounts)
|
||||
}
|
||||
close(readyRecords)
|
||||
var consumed []int
|
||||
for ready := range readyRecords {
|
||||
consumed = append(consumed, ready.HookExclusions)
|
||||
}
|
||||
if len(consumed) != 2 || consumed[0] != 2 || consumed[1] != 0 {
|
||||
t.Fatalf("consumed ready counts = %v, want [2 0]", consumed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperStartRecordMirrorsProgramsAndExtra(t *testing.T) {
|
||||
t.Setenv("WINE_ACCESS_NO_DIALOG_READER", "")
|
||||
root := t.TempDir()
|
||||
helper := filepath.Join(root, "helper")
|
||||
if err := os.WriteFile(helper, []byte("#!/bin/sh\nexec sleep 30\n"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
handler := &channelLogHandler{records: make(chan slog.Record, 32)}
|
||||
manager := &Manager{helper: helper, logger: slog.New(handler),
|
||||
ExcludedPrograms: []string{"Game.EXE", "game.exe", "Another.exe"}}
|
||||
child := &process{}
|
||||
source := prefix{path: root, programs: map[string]bool{"launcher.exe": true, "game.exe": true}}
|
||||
if err := manager.launch(child, source, time.Now()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stop(child)
|
||||
deadline := time.After(2 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case record := <-handler.records:
|
||||
if record.Message != "Wine accessibility helper started" {
|
||||
continue
|
||||
}
|
||||
attrs := map[string]string{}
|
||||
record.Attrs(func(attr slog.Attr) bool {
|
||||
attrs[attr.Key] = attr.Value.String()
|
||||
return true
|
||||
})
|
||||
if attrs["programs"] != "['game.exe', 'launcher.exe']" {
|
||||
t.Fatalf("programs = %q", attrs["programs"])
|
||||
}
|
||||
want := "['--exclude-hook-program', 'another.exe', '--exclude-hook-program', 'game.exe']"
|
||||
if attrs["extra"] != want {
|
||||
t.Fatalf("extra = %q, want %q", attrs["extra"], want)
|
||||
}
|
||||
return
|
||||
case <-deadline:
|
||||
t.Fatal("helper-start record missing")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type channelLogHandler struct {
|
||||
records chan slog.Record
|
||||
attrs []slog.Attr
|
||||
|
||||
Reference in new issue
Block a user