Files
olcrtc/mobile/mobile_test.go
2026-05-07 16:36:30 +03:00

418 lines
10 KiB
Go

//nolint:all // Test file keeps scenario setup inline.
package mobile
import (
"context"
"errors"
"log"
"strings"
"sync"
"testing"
"time"
"github.com/openlibrecommunity/olcrtc/internal/logger"
"github.com/openlibrecommunity/olcrtc/internal/protect"
)
type testProtector struct {
called int
}
func (p *testProtector) Protect(fd int) bool {
p.called = fd
return true
}
type testLogWriter struct {
got string
}
func (w *testLogWriter) WriteLog(msg string) {
w.got += msg
}
func resetMobileGlobals(t *testing.T) {
t.Helper()
mu.Lock()
if cancel != nil {
cancel()
}
cancel = nil
done = nil
ready = nil
errRun = nil
runClientWithReady = clientRunWithReady
defaults = mobileConfig{}
defaultsSet = sync.Once{}
mu.Unlock()
protect.Protector = nil
logger.SetVerbose(false)
}
var clientRunWithReady = runClientWithReady
func TestProtectorAndLogging(t *testing.T) {
resetMobileGlobals(t)
p := &testProtector{}
SetProtector(p)
if protect.Protector == nil || !protect.Protector(123) || p.called != 123 {
t.Fatal("SetProtector() did not install adapter")
}
SetProtector(nil)
if protect.Protector != nil {
t.Fatal("SetProtector(nil) did not clear protector")
}
w := &testLogWriter{}
SetLogWriter(w)
log.Print("hello")
if !strings.Contains(w.got, "hello") {
t.Fatalf("log writer got %q, want hello", w.got)
}
}
func TestDefaultsAndSetters(t *testing.T) {
resetMobileGlobals(t)
SetTransport("dc")
SetLink("direct")
SetDNS("9.9.9.9:53")
SetVP8Options(-1, 999)
mu.Lock()
got := defaults
mu.Unlock()
if got.transport != dataTransport || got.link != defaultLink || got.dnsServer != "9.9.9.9:53" ||
got.vp8FPS != 1 || got.vp8BatchSize != 64 {
t.Fatalf("defaults = %+v", got)
}
SetDebug(true)
if !logger.IsVerbose() {
t.Fatal("SetDebug(true) did not enable verbose")
}
SetDebug(false)
if logger.IsVerbose() {
t.Fatal("SetDebug(false) did not disable verbose")
}
}
func TestNormalizeBuildRoomAndClamp(t *testing.T) {
tests := map[string]string{
"datachannel": dataTransport,
"data": dataTransport,
"dc": dataTransport,
"vp8channel": defaultTransport,
"vp8": defaultTransport,
"bad": defaultTransport,
}
for in, want := range tests {
if got := normalizeTransport(in); got != want {
t.Fatalf("normalizeTransport(%q) = %q, want %q", in, got, want)
}
}
if normalizeCarrier(carrierWBStream) != carrierWBStream || normalizeCarrier("jazz") != "jazz" {
t.Fatal("normalizeCarrier() returned unexpected value")
}
if got := buildRoomURL("telemost", "abc"); got != "https://telemost.yandex.ru/j/abc" {
t.Fatalf("telemost room URL = %q", got)
}
if got := buildRoomURL("jazz", ""); got != "any" {
t.Fatalf("jazz empty room URL = %q", got)
}
if got := buildRoomURL(carrierWBStream, "room"); got != "room" {
t.Fatalf("wbstream room URL = %q", got)
}
if clampAtLeastOne(0, 10) != 1 || clampAtLeastOne(11, 10) != 10 || clampAtLeastOne(5, 10) != 5 {
t.Fatal("clampAtLeastOne() returned unexpected value")
}
}
func TestStartValidation(t *testing.T) {
resetMobileGlobals(t)
if err := startWithConfig("", dataTransport, "room", "client", "key", 1080, "", "", mobileConfig{}); !errors.Is(err, errCarrierRequired) {
t.Fatalf("startWithConfig(missing carrier) = %v", err)
}
if err := startWithConfig("telemost", dataTransport, "", "client", "key", 1080, "", "", mobileConfig{}); !errors.Is(err, errRoomIDRequired) {
t.Fatalf("startWithConfig(missing room) = %v", err)
}
if err := startWithConfig("jazz", dataTransport, "", "", "key", 1080, "", "", mobileConfig{}); !errors.Is(err, errClientIDRequired) {
t.Fatalf("startWithConfig(missing client) = %v", err)
}
if err := startWithConfig("jazz", dataTransport, "", "client", "", 1080, "", "", mobileConfig{}); !errors.Is(err, errKeyHexRequired) {
t.Fatalf("startWithConfig(missing key) = %v", err)
}
mu.Lock()
cancel = func() {}
mu.Unlock()
if err := startWithConfig("jazz", dataTransport, "", "client", "key", 1080, "", "", mobileConfig{}); !errors.Is(err, errAlreadyRunning) {
t.Fatalf("startWithConfig(running) = %v", err)
}
resetMobileGlobals(t)
}
func TestStartWithInjectedRunnerLifecycle(t *testing.T) {
resetMobileGlobals(t)
t.Cleanup(func() {
resetMobileGlobals(t)
})
runClientWithReady = func(
ctx context.Context,
linkName, transportName, carrierName, roomURL, keyHex, clientID string,
localAddr string,
dnsServer, socksUser, socksPass string,
onReady func(),
videoWidth int,
videoHeight int,
videoFPS int,
videoBitrate string,
videoHW string,
videoQRSize int,
videoQRRecovery string,
videoCodec string,
videoTileModule int,
videoTileRS int,
vp8FPS int,
vp8BatchSize int,
seiFPS int,
seiBatchSize int,
seiFragmentSize int,
seiAckTimeoutMS int,
) error {
if linkName != defaultLink || transportName != dataTransport || carrierName != carrierJazz ||
roomURL != "any" || clientID != "client" || localAddr != "127.0.0.1:1080" ||
dnsServer != defaultDNSServer || vp8FPS != 60 || vp8BatchSize != 8 {
t.Fatalf("RunWithReady args mismatch: link=%q transport=%q carrier=%q room=%q client=%q local=%q dns=%q vp8=%d/%d",
linkName, transportName, carrierName, roomURL, clientID, localAddr, dnsServer, vp8FPS, vp8BatchSize)
}
onReady()
<-ctx.Done()
return ctx.Err()
}
if err := StartWithTransport(carrierJazz, "dc", "", "client", "key", 1080, "", ""); err != nil {
t.Fatalf("StartWithTransport() error = %v", err)
}
if !IsRunning() {
t.Fatal("IsRunning() = false, want true")
}
if err := WaitReady(100); err != nil {
t.Fatalf("WaitReady() error = %v", err)
}
Stop()
if IsRunning() {
t.Fatal("IsRunning() = true after Stop")
}
}
func TestStartUsesDefaultsAndCheckWithInjectedRunner(t *testing.T) {
resetMobileGlobals(t)
t.Cleanup(func() {
resetMobileGlobals(t)
})
runClientWithReady = func(
ctx context.Context,
linkName, transportName, carrierName, roomURL, keyHex, clientID string,
localAddr string,
dnsServer, socksUser, socksPass string,
onReady func(),
videoWidth int,
videoHeight int,
videoFPS int,
videoBitrate string,
videoHW string,
videoQRSize int,
videoQRRecovery string,
videoCodec string,
videoTileModule int,
videoTileRS int,
vp8FPS int,
vp8BatchSize int,
seiFPS int,
seiBatchSize int,
seiFragmentSize int,
seiAckTimeoutMS int,
) error {
if transportName != defaultTransport || roomURL != "https://telemost.yandex.ru/j/room" ||
localAddr != "127.0.0.1:1081" || socksUser != "u" || socksPass != "p" {
t.Fatalf("Start args mismatch: transport=%q room=%q local=%q user/pass=%q/%q",
transportName, roomURL, localAddr, socksUser, socksPass)
}
onReady()
<-ctx.Done()
return ctx.Err()
}
if err := Start("telemost", "room", "client", "key", 1081, "u", "p"); err != nil {
t.Fatalf("Start() error = %v", err)
}
if err := WaitReady(100); err != nil {
t.Fatalf("WaitReady() error = %v", err)
}
Stop()
runClientWithReady = func(
ctx context.Context,
linkName, transportName, carrierName, roomURL, keyHex, clientID string,
localAddr string,
dnsServer, socksUser, socksPass string,
onReady func(),
videoWidth int,
videoHeight int,
videoFPS int,
videoBitrate string,
videoHW string,
videoQRSize int,
videoQRRecovery string,
videoCodec string,
videoTileModule int,
videoTileRS int,
vp8FPS int,
vp8BatchSize int,
seiFPS int,
seiBatchSize int,
seiFragmentSize int,
seiAckTimeoutMS int,
) error {
if transportName != dataTransport || vp8FPS != 1 || vp8BatchSize != 64 {
t.Fatalf("Check args mismatch: transport=%q vp8=%d/%d", transportName, vp8FPS, vp8BatchSize)
}
onReady()
<-ctx.Done()
return nil
}
elapsed, err := Check("jazz", "dc", "", "client", "key", 1082, 100, -1, 999)
if err != nil {
t.Fatalf("Check() error = %v", err)
}
if elapsed < 0 {
t.Fatalf("Check() elapsed = %d", elapsed)
}
}
func TestCheckTimeoutAndRunError(t *testing.T) {
resetMobileGlobals(t)
t.Cleanup(func() {
resetMobileGlobals(t)
})
runClientWithReady = func(
ctx context.Context,
linkName, transportName, carrierName, roomURL, keyHex, clientID string,
localAddr string,
dnsServer, socksUser, socksPass string,
onReady func(),
videoWidth int,
videoHeight int,
videoFPS int,
videoBitrate string,
videoHW string,
videoQRSize int,
videoQRRecovery string,
videoCodec string,
videoTileModule int,
videoTileRS int,
vp8FPS int,
vp8BatchSize int,
seiFPS int,
seiBatchSize int,
seiFragmentSize int,
seiAckTimeoutMS int,
) error {
<-ctx.Done()
return nil
}
if _, err := Check("telemost", defaultTransport, "room", "client", "key", 1083, 1, 30, 1); !errors.Is(err, errStartTimedOut) {
t.Fatalf("Check(timeout) error = %v, want %v", err, errStartTimedOut)
}
want := errors.New("check failed")
runClientWithReady = func(
context.Context,
string, string, string, string, string, string,
string,
string, string, string,
func(),
int, int, int,
string,
string,
int,
string,
string,
int,
int,
int,
int,
int,
int,
int,
int,
) error {
return want
}
if _, err := Check("telemost", defaultTransport, "room", "client", "key", 1084, 100, 30, 1); !errors.Is(err, want) {
t.Fatalf("Check(run error) = %v, want %v", err, want)
}
}
func TestWaitReadyStatesAndStop(t *testing.T) {
resetMobileGlobals(t)
if err := WaitReady(1); !errors.Is(err, errNotRunning) {
t.Fatalf("WaitReady(not running) = %v", err)
}
mu.Lock()
errRun = errors.New("run failed")
mu.Unlock()
if err := WaitReady(1); err == nil || err.Error() != "run failed" {
t.Fatalf("WaitReady(run err) = %v", err)
}
mu.Lock()
errRun = nil
ready = make(chan struct{})
done = make(chan struct{})
cancel = func() {}
mu.Unlock()
if err := WaitReady(1); !errors.Is(err, errStartTimedOut) {
t.Fatalf("WaitReady(timeout) = %v", err)
}
mu.Lock()
close(ready)
mu.Unlock()
if err := WaitReady(1); err != nil {
t.Fatalf("WaitReady(ready) error = %v", err)
}
mu.Lock()
cancel = func() {}
done = make(chan struct{})
doneCh := done
mu.Unlock()
go func() {
time.Sleep(time.Millisecond)
close(doneCh)
}()
Stop()
mu.Lock()
cancel = nil
mu.Unlock()
}
func TestLogBridge(t *testing.T) {
w := &testLogWriter{}
n, err := (&logBridge{w: w}).Write([]byte("abc"))
if err != nil || n != 3 || w.got != "abc" {
t.Fatalf("logBridge.Write() = (%d, %v), got %q", n, err, w.got)
}
}