Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,104 @@
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user