mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 20:17:08 +00:00
125 lines
3.1 KiB
Go
125 lines
3.1 KiB
Go
package audio
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// shared between tg->xmpp and xmpp->tg tests
|
|
type fakeNtg struct {
|
|
mu sync.Mutex
|
|
handler NtgFrameHandler
|
|
unregistered int // count of cancel() invocations returned by OnFrame
|
|
sentPCM [][]byte
|
|
micCalls int
|
|
clears int
|
|
micCallCapture []bool // ordered record of capture-flag arg per SetExternalMicrophone call
|
|
sendErr error // if set, SendMicrophonePCM returns this
|
|
}
|
|
|
|
// stashes handler so tests can dispatch directly; cancel just bumps a
|
|
// counter (handler stays reachable so tests can exercise closed-flag guards)
|
|
func (f *fakeNtg) OnFrame(h NtgFrameHandler) func() {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.handler = h
|
|
return func() {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.unregistered++
|
|
}
|
|
}
|
|
func (f *fakeNtg) SetExternalMicrophone(_ int64, capture bool, _ uint32, _ uint8) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.micCalls++
|
|
f.micCallCapture = append(f.micCallCapture, capture)
|
|
return nil
|
|
}
|
|
func (f *fakeNtg) ClearStreams(int64) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.clears++
|
|
return nil
|
|
}
|
|
func (f *fakeNtg) SendMicrophonePCM(_ int64, pcm []byte) error {
|
|
cp := make([]byte, len(pcm))
|
|
copy(cp, pcm)
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
err := f.sendErr
|
|
f.sentPCM = append(f.sentPCM, cp)
|
|
return err
|
|
}
|
|
|
|
func (f *fakeNtg) sentCount() int {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return len(f.sentPCM)
|
|
}
|
|
|
|
func newTestTgToXmpp(t *testing.T) (*TgToXmpp, *fakeNtg) {
|
|
t.Helper()
|
|
n := &fakeNtg{}
|
|
h, err := NewTgToXmpp(TgToXmppOptions{
|
|
Ntg: n,
|
|
ChatID: 12345,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewTgToXmpp: %v", err)
|
|
}
|
|
return h, n
|
|
}
|
|
|
|
func TestTgToXmppStartConfiguresPlayback(t *testing.T) {
|
|
h, n := newTestTgToXmpp(t)
|
|
if err := h.Start(); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
defer h.Close()
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
if n.micCalls != 1 {
|
|
t.Errorf("SetExternalMicrophone called %d times, want 1 (playback only)", n.micCalls)
|
|
}
|
|
if len(n.micCallCapture) != 1 || n.micCallCapture[0] {
|
|
t.Errorf("expected capture=false (playback) for tg->xmpp, got %v", n.micCallCapture)
|
|
}
|
|
}
|
|
|
|
func TestTgToXmppCloseStopsCallbackHandling(t *testing.T) {
|
|
h, n := newTestTgToXmpp(t)
|
|
if err := h.Start(); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
if err := h.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
// Post-close dispatches must be ignored.
|
|
frame := PCMFrame{SSRC: 1, Data: make([]byte, NtgFrameBytes)}
|
|
n.handler(h.opts.ChatID, []PCMFrame{frame, frame, frame, frame})
|
|
if got := len(h.acc); got != 0 {
|
|
t.Errorf("closed half accumulated %d bytes, want 0", got)
|
|
}
|
|
}
|
|
|
|
// mirrors ntgcalls' callback-per-goroutine dispatch; run with -race
|
|
func TestTgToXmppFeedFrameConcurrent(t *testing.T) {
|
|
h, _ := newTestTgToXmpp(t)
|
|
const N = 100
|
|
var wg sync.WaitGroup
|
|
wg.Add(N)
|
|
for i := 0; i < N; i++ {
|
|
go func() {
|
|
defer wg.Done()
|
|
f := PCMFrame{SSRC: 42, Data: make([]byte, NtgFrameBytes)}
|
|
h.feedFrame(f)
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
if got := len(h.acc); got != 0 && got != NtgFrameBytes {
|
|
t.Errorf("acc len=%d after %d concurrent feeds; want 0 or %d", got, N, NtgFrameBytes)
|
|
}
|
|
}
|