// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "context" "log/slog" "os" "os/exec" "strings" "testing" "time" "git.stormux.org/storm/cthulhu/internal/atspi" "git.stormux.org/storm/cthulhu/internal/braille" ) func TestNativeAudacious(t *testing.T) { if os.Getenv("CTHULHU_NATIVE_AUDACIOUS") == "" { t.Skip("requires private Audacious GUI session") } ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() adapter := atspi.New() if err := adapter.Start(ctx); err != nil { t.Fatal(err) } defer adapter.Stop(context.Background()) queue := &autocompleteRequestQueue{requests: make(chan speechRequest, 256)} frames := make(chan braille.Frame, 256) done := make(chan struct{}) go func() { defer close(done) processEventsWithInput(ctx, slog.New(slog.DiscardHandler), outputQueue{speech: queue, braille: frameRecorder{frames: frames}}, adapter, nil, nil, 0, nil, nil, nil, nil) }() defer func() { cancel(); <-done }() keys := func(chord string) { t.Helper() t.Logf("KEY %s", chord) if out, err := exec.CommandContext(ctx, "xdotool", "key", "--clearmodifiers", chord).CombinedOutput(); err != nil { t.Fatalf("keys: %v %s", err, out) } } wait := func(wantSpeech, wantBraille string) { t.Helper() spoken, displayed := wantSpeech == "", wantBraille == "" timer := time.NewTimer(2 * time.Second) defer timer.Stop() for { select { case request := <-queue.requests: t.Logf("speech %s: %q", request.Cause, request.Text) if request.Cause == atspi.CaretEvent && strings.Contains(request.Text, "password text") { t.Fatalf("unfocused proxy password initialization was spoken: %q", request.Text) } if strings.Contains(request.Text, ".wav") || strings.Contains(request.Text, "/tmp/") { t.Fatalf("hidden playlist columns were spoken: %q", request.Text) } spoken = spoken || strings.Contains(request.Text, wantSpeech) case frame := <-frames: t.Logf("Braille %q", frame.Text) displayed = displayed || strings.Contains(frame.Text, wantBraille) case <-timer.C: if spoken && displayed { return } out, _ := exec.CommandContext(ctx, "/usr/bin/python3", "../../tests/native_audacious.py", "--diagnose").CombinedOutput() t.Logf("native selection: %s", out) t.Fatalf("missing output speech=%q (%v) Braille=%q (%v)", wantSpeech, spoken, wantBraille, displayed) case <-ctx.Done(): t.Fatal(ctx.Err()) } if spoken && displayed { timer.Reset(80 * time.Millisecond) } } } step := func(chord, speech, braille string) { t.Helper() keys(chord) wait(speech, braille) } // Queueing fixtures never starts playback. Select a row explicitly before // testing return from menus: an initially unselected table has no row. step("alt+f", "Open Files ..., menu item", "Open Files ...") if os.Getenv("CTHULHU_NATIVE_AUDACIOUS") == "-Q" { step("Down", "Open Folder ..., menu item", "Open Folder ...") step("Down", "Open URL ..., menu item", "Open URL ...") step("Up", "Open Folder ..., menu item", "Open Folder ...") } else { step("Down", "Open URL ..., menu item", "Open URL ...") step("Up", "Open Files ..., menu item", "Open Files ...") } step("Right", "Play, menu item", "Play") for _, name := range []string{"Pause", "Stop", "Previous", "Next", "Previous Album", "Next Album", "Repeat"} { step("Down", name+", ", name) } step("Return", "Alpha, Example Artist, Example Album, 0:01", "Alpha table cell") step("Down", "Beta, Example Artist, Example Album, 0:01", "Beta table cell") step("Up", "Alpha, Example Artist, Example Album, 0:01", "Alpha table cell") step("alt+p", "Play, menu item", "Play") for _, name := range []string{"Pause", "Stop", "Previous", "Next", "Previous Album", "Next Album"} { step("Down", name+", ", name) } step("Down", "Repeat, check menu item, checked", " Repeat check menu item") step("Return", "Alpha, Example Artist, Example Album, 0:01", "Alpha table cell") step("alt+p", "Play, menu item", "Play") step("Right", "Play/Resume, menu item", "Play/Resume") step("Down", "Refresh, menu item", "Refresh") if os.Getenv("CTHULHU_NATIVE_AUDACIOUS") == "-Q" { step("Down", "Find ..., menu item", "Find ...") } step("Down", "Sort, menu", "Sort") step("Right", "By Track Number, menu item", "By Track Number") step("Down", "By Title, menu item", "By Title") step("Up", "By Track Number, menu item", "By Track Number") step("Left", "Sort, menu", "Sort") if os.Getenv("CTHULHU_NATIVE_AUDACIOUS") == "-Q" { step("Escape", "Playlist, menu item", "Playlist") } step("Escape", "Alpha, Example Artist, Example Album, 0:01", "Alpha table cell") step("ctrl+p", "Playlist Manager", "New Playlist table cell") step("Tab", "New, button", "New button") step("shift+Tab", "New Playlist, 3", "New Playlist table cell") step("Escape", "Alpha, Example Artist, Example Album, 0:01", "Alpha table cell") step("alt+f", "Open Files ..., menu item", "Open Files ...") fileCommands := []string{"Open URL ...", "Add Files ...", "Add URL ...", "Search Library", "About ...", "Settings ..."} if os.Getenv("CTHULHU_NATIVE_AUDACIOUS") == "-Q" { fileCommands = []string{"Open Folder ...", "Open URL ...", "Add Files ...", "Add Folder ...", "Add URL ...", "Search Library", "About ...", "Settings ..."} } for _, name := range fileCommands { step("Down", name+", menu item", name) } if os.Getenv("CTHULHU_NATIVE_AUDACIOUS") == "-Q" { step("Return", "Audacious Settings", "Restart in") step("Tab", "Qt Interface, combo box", "Qt Interface combo box") step("Tab", "Native, combo box", "Native combo box") step("shift+Tab", "Qt Interface, combo box", "Qt Interface combo box") } else { step("Return", "Audacious Settings", "Appearance") step("Tab", "Restart in Qt mode, button", "Restart in Qt mode button") step("Tab", "GTK Interface, combo box", "GTK Interface combo box") step("shift+Tab", "Restart in Qt mode, button", "Restart in Qt mode button") } step("Escape", "Alpha, Example Artist, Example Album, 0:01", "Alpha table cell") // Qt opens this popup without an active item; Down chooses the first. keys("Menu") step("Down", "Song Info ..., menu item", "Song Info ...") step("Down", "Queue/Unqueue, menu item", "Queue/Unqueue") step("Up", "Song Info ..., menu item", "Song Info ...") step("Escape", "Alpha, Example Artist, Example Album, 0:01", "Alpha table cell") }