Files
cthulhu/internal/atspi/mumble_native_test.go
Storm Dragon 3dbd7f2554 Avoid repeating Mumble server labels across columns
Collapse identical full-row labels only for Mumble while preserving mixed column data and repeated values from other providers. Add Qt native coverage and document live saved-server listening confirmation.
2026-11-07 20:12:25 -05:00

159 lines
4.8 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package atspi
import (
"bufio"
"context"
"fmt"
"os"
"os/exec"
"strings"
"testing"
"time"
)
func TestNativeMumbleTranscriptCapture(t *testing.T) {
if os.Getenv("CTHULHU_NATIVE_MUMBLE") != "1" {
t.Skip("requires private Qt/Xorg/AT-SPI session")
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
adapter := New()
if err := adapter.Start(ctx); err != nil {
t.Fatal(err)
}
defer adapter.Stop(context.Background())
for _, process := range []string{"mumble", "messaging-test"} {
t.Run(process, func(t *testing.T) {
command := exec.CommandContext(ctx, "python3", "../../tests/qt_messaging.py", process)
input, err := command.StdinPipe()
if err != nil {
t.Fatal(err)
}
output, err := command.StdoutPipe()
if err != nil {
t.Fatal(err)
}
command.Stderr = os.Stderr
if err := command.Start(); err != nil {
t.Fatal(err)
}
defer func() { input.Close(); command.Process.Kill(); command.Wait() }()
line, err := bufio.NewReader(output).ReadString('\n')
fields := strings.Fields(line)
if err != nil || len(fields) != 2 || fields[0] != "ready" {
t.Fatalf("fixture readiness: %q, %v", line, err)
}
if _, err := adapter.CaptureDesktop(ctx); err != nil {
t.Fatal(err)
}
if out, err := exec.CommandContext(ctx, "xdotool", "windowfocus", "--sync", fields[1]).CombinedOutput(); err != nil {
t.Fatalf("focus: %s, %v", out, err)
}
fmt.Fprintln(input, "messages\nbarrier")
var messages []string
for {
select {
case event := <-adapter.Events():
if event.Source.ApplicationProcess != process {
continue
}
if message := event.ChatMessage; message != nil {
if process != "mumble" {
t.Fatal("unrelated process received chat metadata")
}
if message.Provider != event.Source.BusName || message.Conversation != event.Source.Path || message.Window == "" || message.RoomName != "Mumble" {
t.Fatalf("incomplete message identities: %+v", message)
}
if message.Text != "" {
messages = append(messages, message.Text)
}
}
if event.Type == TextInsertedEvent && event.Payload == "Messaging barrier" {
want := "Username: hello world 界|Repeated message|Repeated message"
if process == "mumble" && strings.Join(messages, "|") != want {
t.Fatalf("captured messages = %q", messages)
}
return
}
case <-ctx.Done():
t.Fatalf("transcript/barrier missing: %v (messages=%q)", ctx.Err(), messages)
}
}
})
}
}
func TestNativeMumbleTableRows(t *testing.T) {
if os.Getenv("CTHULHU_NATIVE_MUMBLE") != "1" {
t.Skip("requires private Qt/Xorg/AT-SPI session")
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
adapter := New()
if err := adapter.Start(ctx); err != nil {
t.Fatal(err)
}
defer adapter.Stop(context.Background())
for _, process := range []string{"mumble", "table-test"} {
t.Run(process, func(t *testing.T) {
command := exec.CommandContext(ctx, "python3", "../../tests/qt_mumble_table.py", process)
output, err := command.StdoutPipe()
if err != nil {
t.Fatal(err)
}
command.Stderr = os.Stderr
if err := command.Start(); err != nil {
t.Fatal(err)
}
defer func() { command.Process.Kill(); command.Wait() }()
line, err := bufio.NewReader(output).ReadString('\n')
fields := strings.Fields(line)
if err != nil || len(fields) != 2 || fields[0] != "ready" {
t.Fatalf("fixture readiness: %q, %v", line, err)
}
if _, err := adapter.CaptureDesktop(ctx); err != nil {
t.Fatal(err)
}
if out, err := exec.CommandContext(ctx, "xdotool", "windowfocus", "--sync", fields[1]).CombinedOutput(); err != nil {
t.Fatalf("focus: %s, %v", out, err)
}
for rowIndex, label := range []string{"Server Alpha", "Server Beta", "Server Gamma, 界"} {
key := "Down"
if rowIndex == 0 {
key = "Home"
}
if out, err := exec.CommandContext(ctx, "xdotool", "key", key).CombinedOutput(); err != nil {
t.Fatalf("select row: %s, %v", out, err)
}
selected:
for {
select {
case <-ctx.Done():
t.Fatalf("row %d (%s) missing: %v", rowIndex, label, ctx.Err())
case event := <-adapter.Events():
cell := event.Source
if cell.ApplicationProcess != process || cell.Name != label || cell.TableColumnCount != 3 {
continue
}
want := strings.Join([]string{label, label, label}, ", ")
if process == "mumble" {
want = label
}
if rowIndex == 1 {
want = label + ", 12, 12"
}
if cell.TableRowText != want || !cell.GUITable || cell.TableRowIndex != rowIndex {
t.Fatalf("row %d context = %+v, want %q", rowIndex, cell, want)
}
break selected
}
}
}
})
}
}