Reset master to testing content
This commit is contained in:
1 parent
0f2587b719
commit
42afa2a56c
1483 files changed
+145308
-938069
No files matched your search
@@ -0,0 +1,183 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package speech
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestColdSpeechConnectionAutostartsAndRetriesSelectedEndpoint(t *testing.T) {
|
||||
for _, connectError := range []error{syscall.ENOENT, syscall.ECONNREFUSED} {
|
||||
t.Run(connectError.Error(), func(t *testing.T) {
|
||||
endpoint := serverAddress{network: "unix", address: "/tmp/selected-speech.sock"}
|
||||
client, server := net.Pipe()
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
attempts, starts := 0, 0
|
||||
got, err := connectWithAutospawn(context.Background(), endpoint, func(_ context.Context, network, address string) (net.Conn, error) {
|
||||
if network != endpoint.network || address != endpoint.address {
|
||||
t.Fatal("connection address changed during autostart")
|
||||
}
|
||||
attempts++
|
||||
if attempts == 1 {
|
||||
return nil, connectError
|
||||
}
|
||||
return client, nil
|
||||
}, func(_ context.Context, selected serverAddress) error {
|
||||
if selected != endpoint {
|
||||
t.Fatal("spawn address differs from selected server")
|
||||
}
|
||||
starts++
|
||||
return nil
|
||||
})
|
||||
if err != nil || got != client || attempts != 2 || starts != 1 {
|
||||
t.Fatalf("connection = %v, %v; attempts %d, starts %d", got, err, attempts, starts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutospawnKeepsRefusalAndCancellationErrors(t *testing.T) {
|
||||
dial := func(context.Context, string, string) (net.Conn, error) { return nil, syscall.ECONNREFUSED }
|
||||
starts := 0
|
||||
spawn := func(context.Context, serverAddress) error { starts++; return errors.New("autospawn disabled") }
|
||||
_, err := connectWithAutospawn(context.Background(), serverAddress{}, dial, spawn)
|
||||
if !errors.Is(err, syscall.ECONNREFUSED) || !strings.Contains(err.Error(), "autospawn disabled") || starts != 1 {
|
||||
t.Fatalf("error = %v, starts = %d", err, starts)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
_, _ = connectWithAutospawn(ctx, serverAddress{}, dial, spawn)
|
||||
if starts != 1 {
|
||||
t.Fatal("canceled connection started a daemon")
|
||||
}
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
_, err = connectWithAutospawn(ctx, serverAddress{}, func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
return nil, syscall.ECONNREFUSED
|
||||
}, func(context.Context, serverAddress) error { cancel(); return nil })
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Fatalf("retry error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpeechSpawnArgumentsPreserveEndpointAndRejectRemote(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
endpoint serverAddress
|
||||
want []string
|
||||
}{
|
||||
{serverAddress{"unix", "/tmp/custom.sock"}, []string{"--spawn", "--communication-method", "unix_socket", "--socket-path", "/tmp/custom.sock"}},
|
||||
{serverAddress{"tcp4", "127.0.0.1:6561"}, []string{"--spawn", "--communication-method", "inet_socket", "--port", "6561"}},
|
||||
} {
|
||||
args, err := spawnArguments(context.Background(), test.endpoint)
|
||||
if err != nil || !reflect.DeepEqual(args, test.want) {
|
||||
t.Fatalf("arguments = %v, %v", args, err)
|
||||
}
|
||||
}
|
||||
if _, err := spawnArguments(context.Background(), serverAddress{"tcp4", "192.0.2.1:6560"}); err == nil {
|
||||
t.Fatal("remote server allowed local autospawn")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpeechSpawnUsesConfiguredExecutableAndReportsRefusal(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "fake-speech-dispatcher")
|
||||
if err := os.WriteFile(path, []byte("#!/bin/sh\necho 'autospawn disabled by daemon' >&2\nexit 1\n"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("SPEECHD_CMD", path)
|
||||
err := spawnServer(context.Background(), serverAddress{"unix", "/tmp/custom.sock"})
|
||||
if err == nil || !strings.Contains(err.Error(), "autospawn disabled by daemon") {
|
||||
t.Fatalf("error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExistingSpeechServerDoesNotInvokeSpawn(t *testing.T) {
|
||||
t.Setenv("SPEECHD_CMD", "/nonexistent/speech-dispatcher")
|
||||
path, _ := newSettingsServer(t, func(string) string { return "" })
|
||||
client := New(path)
|
||||
if err := client.Connect(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close(context.Background())
|
||||
}
|
||||
|
||||
// Run against a separate dummy-output daemon, never the user's speech server.
|
||||
func TestNativeSpeechDispatcherColdStart(t *testing.T) {
|
||||
if os.Getenv("CTHULHU_NATIVE_SPEECH_STARTUP") != "1" {
|
||||
t.Skip("requires isolated Speech Dispatcher startup")
|
||||
}
|
||||
if _, err := exec.LookPath("speech-dispatcher"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, staleSocket := range []bool{false, true} {
|
||||
t.Run(fmt.Sprintf("stale-socket-%v", staleSocket), func(t *testing.T) {
|
||||
directory, err := os.MkdirTemp("", "speech-start-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(directory)
|
||||
configPath := filepath.Join(directory, "speechd.conf")
|
||||
config := "LogLevel 0\nLogDir \"" + directory + "\"\nAddModule \"dummy\" \"sd_dummy\" \"\"\nDefaultModule dummy\nTimeout 1\n"
|
||||
if err := os.WriteFile(configPath, []byte(config), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pidPath := filepath.Join(directory, "speechd.pid")
|
||||
wrapper := filepath.Join(directory, "spawn")
|
||||
script := "#!/bin/sh\nexec speech-dispatcher --config-dir \"$CTHULHU_TEST_SPEECH_CONFIG\" --pid-file \"$CTHULHU_TEST_SPEECH_PID\" \"$@\"\n"
|
||||
if err := os.WriteFile(wrapper, []byte(script), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("CTHULHU_TEST_SPEECH_CONFIG", directory)
|
||||
t.Setenv("CTHULHU_TEST_SPEECH_PID", pidPath)
|
||||
t.Setenv("SPEECHD_CMD", wrapper)
|
||||
t.Setenv("XDG_RUNTIME_DIR", directory)
|
||||
t.Setenv("SPEECHD_ADDRESS", "")
|
||||
t.Setenv("SPEECHD_SOCKET", "")
|
||||
defer func() {
|
||||
if data, err := os.ReadFile(pidPath); err == nil {
|
||||
if pid, err := strconv.Atoi(strings.TrimSpace(string(data))); err == nil {
|
||||
_ = syscall.Kill(pid, syscall.SIGTERM)
|
||||
}
|
||||
}
|
||||
}()
|
||||
socket := DefaultSocketAddress()
|
||||
if staleSocket {
|
||||
if err := os.MkdirAll(filepath.Dir(socket), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
listener, err := net.ListenUnix("unix", &net.UnixAddr{Name: socket, Net: "unix"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
listener.SetUnlinkOnClose(false)
|
||||
listener.Close()
|
||||
}
|
||||
client := New("")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
defer client.Close(context.Background())
|
||||
if err := client.Connect(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if client.clientID <= 0 || client.cancelConn == nil {
|
||||
t.Fatal("cold-start client did not initialize both channels")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user