- 只翻注释/文档字符串,代码零变化:go/scanner 词法流比对 21 文件 token+字面量全等,增删行数 1:1(467/467) - 刻意保留非散文英文:JSON Schema description、工具 Description() 返回值、adapter prompt、API 路径与命令示例、环境变量名 - 逐行确认代码前缀未变,未触碰其他模块
109 lines
2.4 KiB
Go
109 lines
2.4 KiB
Go
package voice
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestService_STTAndTTS(t *testing.T) {
|
|
t.Parallel()
|
|
chain := NewChain(
|
|
NewProvider("cloud", &stubSTT{text: "transcribed"}, &stubTTS{audio: Audio{Format: "mp3", Data: []byte("a")}}),
|
|
)
|
|
svc := NewService(chain)
|
|
defer svc.Close()
|
|
|
|
text, err := svc.STT(context.Background(), Audio{Format: "wav", Data: []byte("x")})
|
|
if err != nil {
|
|
t.Fatalf("STT error: %v", err)
|
|
}
|
|
if text != "transcribed" {
|
|
t.Errorf("text = %q, want transcribed", text)
|
|
}
|
|
|
|
audio, err := svc.TTS(context.Background(), "hello")
|
|
if err != nil {
|
|
t.Fatalf("TTS error: %v", err)
|
|
}
|
|
if string(audio.Data) != "a" || audio.Format != "mp3" {
|
|
t.Errorf("audio = %+v, want mp3/a", audio)
|
|
}
|
|
}
|
|
|
|
func TestService_CloseStopsNewCalls(t *testing.T) {
|
|
t.Parallel()
|
|
svc := NewService(NewChain(SilentProvider()))
|
|
svc.Close()
|
|
_, err := svc.STT(context.Background(), Audio{})
|
|
if !errors.Is(err, ErrClosed) {
|
|
t.Fatalf("err = %v, want ErrClosed", err)
|
|
}
|
|
}
|
|
|
|
// blockingSTT 会阻塞到它的 context 被取消,同时标记它已启动。
|
|
type blockingSTT struct {
|
|
started chan struct{}
|
|
once sync.Once
|
|
}
|
|
|
|
func (b *blockingSTT) STT(ctx context.Context, _ Audio) (string, error) {
|
|
b.once.Do(func() { close(b.started) })
|
|
<-ctx.Done()
|
|
return "", ctx.Err()
|
|
}
|
|
|
|
func TestService_CloseCancelsInFlightWorker(t *testing.T) {
|
|
t.Parallel()
|
|
blk := &blockingSTT{started: make(chan struct{})}
|
|
chain := NewChain(NewProvider("cloud", blk, nil))
|
|
svc := NewService(chain)
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
_, _ = svc.STT(context.Background(), Audio{Format: "wav", Data: []byte("x")})
|
|
}()
|
|
|
|
select {
|
|
case <-blk.started:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("provider never started")
|
|
}
|
|
|
|
svc.Close()
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("STT call did not return after Close; worker likely leaked")
|
|
}
|
|
}
|
|
|
|
func TestService_CallerCancelUnblocks(t *testing.T) {
|
|
t.Parallel()
|
|
blk := &blockingSTT{started: make(chan struct{})}
|
|
svc := NewService(NewChain(NewProvider("cloud", blk, nil)))
|
|
defer svc.Close()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
_, _ = svc.STT(ctx, Audio{Format: "wav", Data: []byte("x")})
|
|
}()
|
|
|
|
<-blk.started
|
|
cancel()
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("caller cancellation did not unblock STT")
|
|
}
|
|
}
|