Files
cthulhu/internal/app/progress_native_test.go

129 lines
3.8 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"bufio"
"context"
"fmt"
"log/slog"
"os"
"os/exec"
"strings"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
"git.stormux.org/storm/cthulhu/internal/config"
)
func TestNativeProgressBars(t *testing.T) {
if os.Getenv("CTHULHU_NATIVE_PROGRESS") != "1" {
t.Skip("requires private X11/AT-SPI progress fixture")
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
adapter := atspi.New()
if err := adapter.Start(ctx); err != nil {
t.Fatal(err)
}
defer func() {
stopCtx, stopCancel := context.WithTimeout(context.Background(), 3*time.Second)
defer stopCancel()
if err := adapter.Stop(stopCtx); err != nil {
t.Error(err)
}
}()
fixture := exec.CommandContext(ctx, "python3", "../../tests/gtk_progress.py")
fixture.Stderr = os.Stderr
commands, err := fixture.StdinPipe()
if err != nil {
t.Fatal(err)
}
ready, err := fixture.StdoutPipe()
if err != nil {
t.Fatal(err)
}
if err := fixture.Start(); err != nil {
t.Fatal(err)
}
defer func() { commands.Close(); fixture.Process.Kill(); fixture.Wait() }()
line, err := bufio.NewReader(ready).ReadString('\n')
if err != nil || strings.TrimSpace(line) != "ready" {
t.Fatalf("fixture: %q %v", line, err)
}
focus := func(title string) {
t.Helper()
windows, err := exec.Command("xdotool", "search", "--onlyvisible", "--name", "^"+title+"$").Output()
if err != nil {
t.Fatal(err)
}
if err := exec.Command("xdotool", "windowfocus", "--sync", strings.Fields(string(windows))[0]).Run(); err != nil {
t.Fatal(err)
}
}
focus("Progress probe")
settings := config.DefaultSettings()
settings.ProgressBar.OutputMode = "both"
live := &liveSettings{}
live.Store(settings)
speech := &fakeSpeechQueue{spoken: make(chan string, 16), requests: make(chan speechRequest, 16)}
state := &eventLoopState{}
handler := &accessibilityEventHandler{state: state, live: live, speech: speech, logger: slog.New(slog.DiscardHandler)}
update := func(value float64, hidden ...bool) atspi.Event {
t.Helper()
command := fmt.Sprint(value)
if len(hidden) != 0 && hidden[0] {
command = "hide " + command
}
if _, err := fmt.Fprintln(commands, command); err != nil {
t.Fatal(err)
}
for {
select {
case <-ctx.Done():
t.Fatal("progress value event missing")
return atspi.Event{}
case event := <-adapter.Events():
if isValueChangeEvent(event.Type) && event.Source.Role == "progress bar" && event.Source.CurrentValue == value {
return event
}
}
}
}
event := update(0.5)
if event.Source.Focused || !event.Source.Visible || !event.Source.Showing || event.ForegroundMismatch {
t.Fatalf("foreground progress capture: %+v", event)
}
state.owner.window, state.owner.applicationBus = event.Window, event.Source.BusName
if !handler.handle_progress_bar(event) {
t.Fatal("foreground progress was lost")
}
request := <-speech.requests
if request.Text != "50 percent" || len(request.Sound) != 1 || request.Sound[0].Name != "generated-progress-tone:50" {
t.Fatalf("foreground progress output: %+v", request)
}
focus("Other probe")
event = update(0.75)
if !event.ForegroundMismatch {
t.Fatal("native background progress was not identified")
}
handler.handle_progress_bar(event)
if len(speech.requests) != 0 {
t.Fatal("background output enabled by default")
}
settings.ProgressBar.ReportBackground = true
live.Store(settings)
handler.handle_progress_bar(event)
request = <-speech.requests
if request.Text != "75 percent" || len(request.Sound) != 1 {
t.Fatalf("background output: %+v", request)
}
event = update(0.9, true)
if event.Source.Showing || handler.handle_progress_bar(event) {
t.Fatal("hidden progress generated automatic output")
}
}