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,171 @@
|
||||
package nvda2cthulhu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type recordingHandler struct {
|
||||
mutex sync.Mutex
|
||||
spoken []string
|
||||
interrupt []bool
|
||||
braille []string
|
||||
canceled int
|
||||
}
|
||||
|
||||
func (handler *recordingHandler) SpeakText(text string, interrupt bool) bool {
|
||||
handler.mutex.Lock()
|
||||
defer handler.mutex.Unlock()
|
||||
handler.spoken = append(handler.spoken, text)
|
||||
handler.interrupt = append(handler.interrupt, interrupt)
|
||||
return true
|
||||
}
|
||||
|
||||
func (handler *recordingHandler) BrailleMessage(text string) bool {
|
||||
handler.mutex.Lock()
|
||||
defer handler.mutex.Unlock()
|
||||
handler.braille = append(handler.braille, text)
|
||||
return true
|
||||
}
|
||||
|
||||
func (handler *recordingHandler) CancelSpeech() bool {
|
||||
handler.mutex.Lock()
|
||||
defer handler.mutex.Unlock()
|
||||
handler.canceled++
|
||||
return true
|
||||
}
|
||||
|
||||
func TestAllowedOriginAcceptsNativeAndLoopbackClients(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
origin string
|
||||
want bool
|
||||
}{
|
||||
{"", true},
|
||||
{"http://127.0.0.1:3457", true},
|
||||
{"http://localhost:3457", true},
|
||||
{"https://[::1]:3457", true},
|
||||
{"https://example.com", false},
|
||||
{"null", false},
|
||||
} {
|
||||
request := httptest.NewRequest(http.MethodGet, "http://127.0.0.1:3457/", nil)
|
||||
if test.origin != "" {
|
||||
request.Header.Set("Origin", test.origin)
|
||||
}
|
||||
if got := allowedOrigin(request); got != test.want {
|
||||
t.Errorf("allowedOrigin(%q) = %v, want %v", test.origin, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func liveSocketTest(t *testing.T) {
|
||||
t.Helper()
|
||||
handler := &recordingHandler{}
|
||||
server := New(nil, handler)
|
||||
httpTest := httptest.NewServer(http.HandlerFunc(server.handleHTTP))
|
||||
defer httpTest.Close()
|
||||
url := "ws" + strings.TrimPrefix(httpTest.URL, "http")
|
||||
connection, _, err := websocket.DefaultDialer.Dial(url, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("dial WebSocket: %v", err)
|
||||
}
|
||||
defer connection.Close()
|
||||
|
||||
if err := connection.WriteMessage(websocket.TextMessage, []byte("Accessibility mod loaded.")); err != nil {
|
||||
t.Fatalf("write speech text: %v", err)
|
||||
}
|
||||
if err := connection.WriteMessage(websocket.BinaryMessage, []byte{0x81, 0xab, 'B', 'r', 'a', 'i', 'l', 'l', 'e', 'T', 'e', 'x', 't', 0xa2, 'O', 'K'}); err != nil {
|
||||
t.Fatalf("write braille text: %v", err)
|
||||
}
|
||||
if err := connection.WriteMessage(websocket.TextMessage, []byte("CancelSpeech")); err != nil {
|
||||
t.Fatalf("write cancellation: %v", err)
|
||||
}
|
||||
|
||||
waitForHandlerCalls(t, handler)
|
||||
handler.mutex.Lock()
|
||||
defer handler.mutex.Unlock()
|
||||
if len(handler.spoken) != 1 || handler.spoken[0] != "Accessibility mod loaded." || len(handler.interrupt) != 1 || !handler.interrupt[0] {
|
||||
t.Fatalf("speech calls = %v, interrupts = %v", handler.spoken, handler.interrupt)
|
||||
}
|
||||
if len(handler.braille) != 1 || handler.braille[0] != "OK" {
|
||||
t.Fatalf("braille calls = %v", handler.braille)
|
||||
}
|
||||
if handler.canceled != 1 {
|
||||
t.Fatalf("cancellations = %d, want 1", handler.canceled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerHandlesWebSocketRequests(t *testing.T) {
|
||||
if os.Getenv("CTHULHU_LIVE_SOCKET_TEST") != "1" {
|
||||
t.Skip("set CTHULHU_LIVE_SOCKET_TEST=1 to allow a local TCP listener")
|
||||
}
|
||||
liveSocketTest(t)
|
||||
}
|
||||
|
||||
func TestStartStopClosesActiveConnection(t *testing.T) {
|
||||
if os.Getenv("CTHULHU_LIVE_SOCKET_TEST") != "1" {
|
||||
t.Skip("set CTHULHU_LIVE_SOCKET_TEST=1 to allow a local TCP listener")
|
||||
}
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("find port: %v", err)
|
||||
}
|
||||
port := listener.Addr().(*net.TCPAddr).Port
|
||||
if err := listener.Close(); err != nil {
|
||||
t.Fatalf("release port: %v", err)
|
||||
}
|
||||
t.Setenv(hostEnv, fmt.Sprintf("http://127.0.0.1:%d", port))
|
||||
|
||||
server := New(nil, &recordingHandler{})
|
||||
if err := server.Start(context.Background()); err != nil {
|
||||
t.Fatalf("start server: %v", err)
|
||||
}
|
||||
connection, _, err := websocket.DefaultDialer.Dial(fmt.Sprintf("ws://127.0.0.1:%d", port), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("dial server: %v", err)
|
||||
}
|
||||
defer connection.Close()
|
||||
if err := server.Stop(context.Background()); err != nil {
|
||||
t.Fatalf("stop server: %v", err)
|
||||
}
|
||||
if _, _, err := connection.ReadMessage(); err == nil {
|
||||
t.Fatal("active connection remained open after Stop")
|
||||
}
|
||||
}
|
||||
|
||||
func waitForHandlerCalls(t *testing.T, handler *recordingHandler) {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
handler.mutex.Lock()
|
||||
done := len(handler.spoken) == 1 && len(handler.braille) == 1 && handler.canceled == 1
|
||||
handler.mutex.Unlock()
|
||||
if done {
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindAddressUsesPythonDefault(t *testing.T) {
|
||||
t.Setenv(hostEnv, "http://127.0.0.1")
|
||||
if address := bindAddress(); address != "127.0.0.1:3457" {
|
||||
t.Fatalf("default address = %q", address)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopWithoutStartSucceeds(t *testing.T) {
|
||||
server := New(nil, nil)
|
||||
if err := server.Stop(context.Background()); err != nil {
|
||||
t.Fatalf("stop unstarted server: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user