Files
olcrtc/mobile/mobile_test.go
2026-05-07 00:06:28 +03:00

397 lines
9.8 KiB
Go

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 clamp(0, 1, 10) != 1 || clamp(11, 1, 10) != 10 || clamp(5, 1, 10) != 5 {
t.Fatal("clamp() 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,
) 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,
) 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,
) 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,
) 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,
) 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)
}
}