// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later //go:build linux && cgo package speech import ( "encoding/hex" "os" "os/exec" "path/filepath" "strings" "testing" ) func TestNativeUSBUsesOptionalABIAndReleasesResources(t *testing.T) { root := t.TempDir() library := filepath.Join(root, "libusb-1.0.so.0") if output, err := exec.Command("cc", "-shared", "-fPIC", "-Wall", "-Wextra", "-Werror", "-o", library, "testdata/usb_fixture.c").CombinedOutput(); err != nil { t.Fatalf("build USB fixture: %v: %s", err, output) } for _, mode := range []string{"ok", "unconfigured", "missing", "multiple", "denied", "busy", "short", "timeout"} { t.Run(mode, func(t *testing.T) { log := filepath.Join(t.TempDir(), "usb.log") cmd := exec.Command(os.Args[0], "-test.run=^TestNativeUSBFixtureProcess$") cmd.Env = append(os.Environ(), "LD_LIBRARY_PATH="+root, "FENRIR_USB_FIXTURE=1", "USB_FIXTURE_MODE="+mode, "USB_FIXTURE_LOG="+log) if output, err := cmd.CombinedOutput(); err != nil { t.Fatalf("fixture process: %v: %s", err, output) } data, err := os.ReadFile(log) if err != nil { t.Fatal(err) } text := string(data) if !strings.HasPrefix(text, "init\nlist\n") || !strings.Contains(text, "free-list\n") || !strings.HasSuffix(text, "exit\n") { t.Fatal("USB cleanup", text) } opened := mode != "missing" && mode != "multiple" && mode != "denied" if strings.Contains(text, "\nclose\n") != opened { t.Fatal("handle cleanup", text) } claimed := opened && mode != "busy" if strings.Contains(text, "\nrelease\n") != claimed { t.Fatal("interface cleanup", text) } if mode == "ok" || mode == "unconfigured" { for _, want := range []string{"write-02:" + hex.EncodeToString([]byte("[:timeout 1]")), "write-02:" + hex.EncodeToString([]byte("[:ra 180]")), "write-02:" + hex.EncodeToString([]byte("hello\x0b")), "write-04:06"} { if !strings.Contains(text, want+"\n") { t.Fatal("missing transfer", want, text) } } } if strings.Contains(text, "set-configuration\n") != (mode == "unconfigured") { t.Fatal("unexpected USB configuration change", text) } }) } } func TestNativeUSBFixtureProcess(t *testing.T) { if os.Getenv("FENRIR_USB_FIXTURE") != "1" { return } transport, err := Open_driver("dectalk-usb", Options{}) mode := os.Getenv("USB_FIXTURE_MODE") if mode != "ok" && mode != "unconfigured" { if err == nil { transport.Close() t.Fatal("USB failure accepted", mode) } return } if err != nil { t.Fatal(err) } defer transport.Close() if err := transport.Configure(Parameters{}); err != nil { t.Fatal(err) } if err := transport.Say("hello"); err != nil { t.Fatal(err) } if err := transport.Cancel(); err != nil { t.Fatal(err) } }