Files
cthulhu/internal/app/braille_helper_test.go

105 lines
3.3 KiB
Go

package app
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log/slog"
"os"
"strings"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
"git.stormux.org/storm/cthulhu/internal/braille"
"git.stormux.org/storm/cthulhu/internal/logging"
)
// The test executable doubles as a real pipe-protocol peer. Only the explicit
// child invocation takes this path; the parent runs the ordinary test suite.
func TestMain(m *testing.M) {
if os.Getenv("CTHULHU_BRAILLE_TEST_FAIL") == "1" && len(os.Args) == 2 && os.Args[1] == "--client" {
fmt.Fprintln(os.Stderr, "braille test startup failure")
os.Exit(1)
}
if os.Getenv("CTHULHU_BRAILLE_TEST_CLIENT") == "1" && len(os.Args) == 2 && os.Args[1] == "--client" {
fmt.Println("ready")
decoder, encoder := json.NewDecoder(os.Stdin), json.NewEncoder(os.Stdout)
for {
var frame braille.Frame
if err := decoder.Decode(&frame); err != nil {
os.Exit(0)
}
if frame.Text != "" {
if err := encoder.Encode(braille.RoutingEvent{ID: frame.ID, Offset: 1}); err != nil {
os.Exit(1)
}
}
}
}
os.Exit(m.Run())
}
func TestBrailleWorkerReportsHelperStartupFailure(t *testing.T) {
t.Setenv("CTHULHU_BRAILLE_TEST_FAIL", "1")
path, err := os.Executable()
if err != nil {
t.Fatal(err)
}
worker := &brailleWorker{path: path, logger: slog.New(slog.DiscardHandler)}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := worker.session(ctx); err == nil || !strings.Contains(err.Error(), "braille test startup failure") {
t.Fatalf("helper startup error = %v", err)
}
}
func TestBrailleWorkerHelperProtocolAndRestart(t *testing.T) {
t.Setenv("CTHULHU_BRAILLE_TEST_CLIENT", "1")
path, err := os.Executable()
if err != nil {
t.Fatal(err)
}
router := &recordingBrailleRouter{calls: make(chan atspi.BrailleRoute, 8)}
var output bytes.Buffer
worker := &brailleWorker{path: path, router: router, logger: logging.New(&output, logging.Options{Level: slog.LevelDebug}), wake: make(chan struct{}, 1)}
stop := func() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := worker.Stop(ctx); err != nil {
t.Errorf("stop helper: %v", err)
}
}
defer stop()
for attempt := 0; attempt < 2; attempt++ {
if err := worker.Start(); err != nil {
t.Fatal(err)
}
worker.EnqueueFrame(braille.Frame{Text: "é猫", Cursor: 0, Routes: []braille.TextRoute{{Start: 0, End: 2, TextLength: 2, SourceStart: 10, BusName: ":1.42", Path: "/entry", SourceText: "é猫"}}})
select {
case route := <-router.calls:
if route.Offset != 11 || route.Focus.Path != "/entry" {
t.Fatalf("route %+v", route)
}
case <-time.After(3 * time.Second):
t.Fatal("helper route was not delivered")
}
stop()
// Stop joins the writer before inspecting its debug output.
for _, expected := range []string{`"message":"sent braille frame"`, `"braille_text":"é猫"`, `"cursor_offset":0`} {
if !strings.Contains(output.String(), expected) {
t.Fatalf("braille diagnostics missing %s: %s", expected, output.String())
}
}
// A duplicate send can be coalesced by either side; discard already handled
// keys before checking that the restarted child returns a new event.
for len(router.calls) > 0 {
<-router.calls
}
if worker.current().Text != "" {
t.Fatal("stopped worker retained old content")
}
}
}