91 lines
3.0 KiB
Go
91 lines
3.0 KiB
Go
//go:build linux && cgo
|
|
|
|
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/config"
|
|
"git.stormux.org/storm/cthulhu/internal/preferences"
|
|
)
|
|
|
|
func test_hardware_switch(t *testing.T, speaker *configuredSpeaker, software config.Settings) {
|
|
t.Helper()
|
|
fd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0600)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
master := os.NewFile(uintptr(fd), "hardware-speech-fixture")
|
|
defer master.Close()
|
|
var unlocked int32
|
|
var number uint32
|
|
if _, _, code := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&unlocked))); code != 0 {
|
|
t.Fatal(code)
|
|
}
|
|
if _, _, code := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&number))); code != 0 {
|
|
t.Fatal(code)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
hardware := software
|
|
hardware.SpeechServerFactory = "hardwarefactory"
|
|
hardware.SpeechOutputModule = "litetalk"
|
|
hardware.HardwareSpeechDevice = fmt.Sprintf("/dev/pts/%d", number)
|
|
hardware.HardwareSpeechBaudRate = 9600
|
|
rate, pitch, volume := 99.0, 9.0, 9.0
|
|
hardware.DefaultVoice = config.Voice{Rate: &rate, AveragePitch: &pitch, Gain: &volume}
|
|
if err := speaker.UpdateConfiguration(ctx, hardware); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
check := func(speak func(context.Context, string) error, text, want string) {
|
|
t.Helper()
|
|
if err := speak(ctx, text); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
master.SetReadDeadline(time.Now().Add(time.Second))
|
|
buffer := make([]byte, len(want))
|
|
if _, err := io.ReadFull(master, buffer); err != nil || string(buffer) != want {
|
|
t.Fatalf("serial=%q want=%q error=%v", buffer, want, err)
|
|
}
|
|
}
|
|
check(speaker.Speak, "Default", "\x019S\x0199P\x019VDefault\r")
|
|
check(speaker.SpeakCharacter, "A", "\x019S\x0199P\x019VA\r")
|
|
check(speaker.SpeakCharacter, "a", "\x019S\x0199P\x019Va\r")
|
|
|
|
worker := newPreferencesWorker(slog.New(slog.DiscardHandler), hardware, speaker)
|
|
values := preferences.FromSettings(hardware)
|
|
rateLow := 0.0
|
|
values.Rate = &rateLow
|
|
if response := worker.apply(ctx, preferences.Request{Values: values}); !response.Accepted {
|
|
t.Fatal(response.Message)
|
|
}
|
|
check(speaker.Speak, "Applied", "\x18\x010S\x0199P\x019VApplied\r")
|
|
values.HardwareSpeechDevice = "/dev/cthulhu-missing-serial-fixture"
|
|
if response := worker.apply(ctx, preferences.Request{Values: values}); response.Accepted {
|
|
t.Fatal("unavailable serial device accepted")
|
|
}
|
|
if worker.settings.HardwareSpeechDevice != hardware.HardwareSpeechDevice {
|
|
t.Fatal("failed Apply committed a missing device")
|
|
}
|
|
check(speaker.Speak, "Retained", "\x010S\x0199P\x019VRetained\r")
|
|
if err := speaker.UpdateConfiguration(ctx, software); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasPrefix(worker.settings.HardwareSpeechDevice, "/dev/pts/") {
|
|
t.Fatal("unexpected fixture device")
|
|
}
|
|
}
|