105 lines
3.5 KiB
Go
105 lines
3.5 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log/slog"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
"git.stormux.org/storm/cthulhu/internal/input"
|
|
"git.stormux.org/storm/cthulhu/internal/platform/xorg"
|
|
)
|
|
|
|
func TestDesktopInputAvailability(t *testing.T) {
|
|
logger := slog.New(slog.DiscardHandler)
|
|
for _, kind := range []string{"x11", "wayland"} {
|
|
t.Run(kind, func(t *testing.T) {
|
|
t.Setenv("XDG_SESSION_TYPE", kind)
|
|
t.Setenv("DISPLAY", "")
|
|
backends := []desktopInputBackend{xorg.New(), xorg.NewCommands([]string{"Insert"}, input.DesktopKeyboardLayout)}
|
|
for _, backend := range backends {
|
|
service := desktopInputService("test", backend, logger)
|
|
err := service.Start(context.Background())
|
|
if (err == nil) != (kind == "wayland") {
|
|
t.Fatalf("%T startup in %s: %v", backend, kind, err)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
if err := service.Stop(ctx); err != nil {
|
|
t.Errorf("%T cleanup: %v", backend, err)
|
|
}
|
|
cancel()
|
|
}
|
|
// Future non-Xorg backends must keep their own failure contract.
|
|
failure := errors.New("other backend failed")
|
|
other := &fakeInputListener{startError: failure}
|
|
if err := desktopInputService("other", other, logger).Start(context.Background()); !errors.Is(err, failure) {
|
|
t.Fatalf("other backend startup: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOptionalXorgPreservesCancellation(t *testing.T) {
|
|
logger := slog.New(slog.DiscardHandler)
|
|
for _, failure := range []error{context.Canceled, context.DeadlineExceeded} {
|
|
if err := startOptionalXorg(context.Background(), "test", func(context.Context) error { return failure }, logger); !errors.Is(err, failure) {
|
|
t.Fatalf("startup: %v, want %v", err, failure)
|
|
}
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
if err := startOptionalXorg(ctx, "test", func(context.Context) error { return nil }, logger); !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("canceled startup: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRunWaylandWithoutX11SpeaksAndStops(t *testing.T) {
|
|
t.Setenv("XDG_SESSION_TYPE", "wayland")
|
|
t.Setenv("DISPLAY", "")
|
|
speaker := &fakeSpeaker{spoken: make(chan string, 8)}
|
|
adapter := &fakeAdapter{events: make(chan atspi.Event, 1)}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
completed := make(chan error, 1)
|
|
go func() {
|
|
completed <- Run(ctx, Options{
|
|
Logger: slog.New(slog.DiscardHandler),
|
|
newSpeaker: func() Speaker { return speaker },
|
|
newSound: func() soundService { return silentSound{} },
|
|
newAdapter: func() Adapter { return adapter },
|
|
})
|
|
}()
|
|
adapter.events <- atspi.NewEvent("focus:", atspi.NewSnapshot("/accessible/save", "Save", "push button", "", ""))
|
|
for _, expected := range []string{"", "Save", "push button"} {
|
|
select {
|
|
case spoken := <-speaker.spoken:
|
|
if expected == "" && contains_string(startupMessages, spoken) || expected != "" && spoken == expected {
|
|
continue
|
|
}
|
|
t.Fatalf("speech = %q, expected %q", spoken, expected)
|
|
case err := <-completed:
|
|
t.Fatalf("runtime exited before focus speech: %v", err)
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("runtime did not present focus with X11 unavailable")
|
|
}
|
|
}
|
|
cancel()
|
|
select {
|
|
case err := <-completed:
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("runtime did not stop")
|
|
}
|
|
if !adapter.stopped || !speaker.closed {
|
|
t.Fatal("runtime did not clean up AT-SPI and speech")
|
|
}
|
|
}
|