|
- package app
-
- import (
- "context"
- "testing"
- "time"
-
- cfgpkg "github.com/jan/fm-rds-tx/internal/config"
- "github.com/jan/fm-rds-tx/internal/platform"
- )
-
- func TestEngineContinuousRun(t *testing.T) {
- cfg := cfgpkg.Default()
- driver := platform.NewSimulatedDriver(nil)
- eng := NewEngine(cfg, driver)
- eng.SetChunkDuration(10 * time.Millisecond)
-
- ctx := context.Background()
- if err := eng.Start(ctx); err != nil {
- t.Fatalf("start: %v", err)
- }
-
- // Let it run for 200ms
- time.Sleep(200 * time.Millisecond)
-
- stats := eng.Stats()
- if stats.State != "running" {
- t.Fatalf("expected running, got %s", stats.State)
- }
- if stats.ChunksProduced < 5 {
- t.Fatalf("expected at least 5 chunks, got %d", stats.ChunksProduced)
- }
- if stats.TotalSamples == 0 {
- t.Fatal("expected non-zero samples")
- }
-
- if err := eng.Stop(ctx); err != nil {
- t.Fatalf("stop: %v", err)
- }
-
- stats = eng.Stats()
- if stats.State != "idle" {
- t.Fatalf("expected idle after stop, got %s", stats.State)
- }
- }
-
- func TestEngineDoubleStartFails(t *testing.T) {
- cfg := cfgpkg.Default()
- driver := platform.NewSimulatedDriver(nil)
- eng := NewEngine(cfg, driver)
-
- ctx := context.Background()
- if err := eng.Start(ctx); err != nil {
- t.Fatalf("first start: %v", err)
- }
- defer eng.Stop(ctx)
-
- if err := eng.Start(ctx); err == nil {
- t.Fatal("expected error on double start")
- }
- }
-
- func TestEngineDriverStats(t *testing.T) {
- cfg := cfgpkg.Default()
- driver := platform.NewSimulatedDriver(nil)
- eng := NewEngine(cfg, driver)
- eng.SetChunkDuration(10 * time.Millisecond)
-
- ctx := context.Background()
- _ = eng.Start(ctx)
- time.Sleep(100 * time.Millisecond)
- _ = eng.Stop(ctx)
-
- driverStats := driver.Stats()
- if driverStats.SamplesWritten == 0 {
- t.Fatal("expected driver to have written samples")
- }
- if driverStats.FramesWritten == 0 {
- t.Fatal("expected driver to have written frames")
- }
- }
-
- func TestEngineSplitRate(t *testing.T) {
- // Simulate Pluto-like config: composite at 228kHz, device at 2.28MHz
- cfg := cfgpkg.Default()
- cfg.Backend.DeviceSampleRateHz = 2280000 // 10:1 ratio
-
- driver := platform.NewSimulatedDriver(nil)
- eng := NewEngine(cfg, driver)
- eng.SetChunkDuration(10 * time.Millisecond)
-
- // Verify split-rate path was chosen
- if eng.upsampler == nil {
- t.Fatal("expected split-rate mode (upsampler != nil)")
- }
-
- ctx := context.Background()
- if err := eng.Start(ctx); err != nil {
- t.Fatalf("start: %v", err)
- }
-
- time.Sleep(200 * time.Millisecond)
-
- stats := eng.Stats()
- if stats.ChunksProduced < 5 {
- t.Fatalf("expected at least 5 chunks, got %d", stats.ChunksProduced)
- }
- // With 10:1 upsampling and 10ms chunks at 228kHz source:
- // 2280 src samples → 22800 output samples per chunk
- // 5 chunks minimum → at least 114000 samples
- if stats.TotalSamples < 50000 {
- t.Fatalf("expected at least 50000 upsampled samples, got %d", stats.TotalSamples)
- }
-
- if err := eng.Stop(ctx); err != nil {
- t.Fatalf("stop: %v", err)
- }
- if stats.Underruns > 0 {
- t.Fatalf("unexpected underruns: %d", stats.Underruns)
- }
- }
-
- func TestEngineSameRate(t *testing.T) {
- // Default config: deviceSampleRateHz=0, compositeRateHz=228000
- // EffectiveDeviceRate = 228000 → same-rate mode, no upsampler
- cfg := cfgpkg.Default()
- driver := platform.NewSimulatedDriver(nil)
- eng := NewEngine(cfg, driver)
-
- if eng.upsampler != nil {
- t.Fatal("expected same-rate mode (upsampler == nil)")
- }
- }
-
- func TestEngineLiveUpdateDSP(t *testing.T) {
- cfg := cfgpkg.Default()
- driver := platform.NewSimulatedDriver(nil)
- eng := NewEngine(cfg, driver)
- eng.SetChunkDuration(10 * time.Millisecond)
-
- ctx := context.Background()
- if err := eng.Start(ctx); err != nil {
- t.Fatalf("start: %v", err)
- }
- defer eng.Stop(ctx)
-
- time.Sleep(50 * time.Millisecond)
-
- // Update DSP params while running
- drive := 1.5
- stereo := false
- err := eng.UpdateConfig(LiveConfigUpdate{
- OutputDrive: &drive,
- StereoEnabled: &stereo,
- })
- if err != nil {
- t.Fatalf("UpdateConfig: %v", err)
- }
-
- // Engine should still be running after update
- time.Sleep(50 * time.Millisecond)
- stats := eng.Stats()
- if stats.State != "running" {
- t.Fatalf("expected running after update, got %s", stats.State)
- }
- if stats.Underruns > 0 {
- t.Fatalf("unexpected underruns after update: %d", stats.Underruns)
- }
- }
-
- func TestEngineLiveUpdateFrequency(t *testing.T) {
- cfg := cfgpkg.Default()
- driver := platform.NewSimulatedDriver(nil)
- eng := NewEngine(cfg, driver)
- eng.SetChunkDuration(10 * time.Millisecond)
-
- ctx := context.Background()
- if err := eng.Start(ctx); err != nil {
- t.Fatalf("start: %v", err)
- }
- defer eng.Stop(ctx)
-
- time.Sleep(50 * time.Millisecond)
-
- // Tune frequency
- freq := 99.5
- err := eng.UpdateConfig(LiveConfigUpdate{FrequencyMHz: &freq})
- if err != nil {
- t.Fatalf("UpdateConfig freq: %v", err)
- }
-
- // Let it process for a bit so the pending freq gets applied
- time.Sleep(50 * time.Millisecond)
- stats := eng.Stats()
- if stats.State != "running" {
- t.Fatalf("expected running after tune, got %s", stats.State)
- }
- }
-
- func TestEngineLiveUpdateRDS(t *testing.T) {
- cfg := cfgpkg.Default()
- driver := platform.NewSimulatedDriver(nil)
- eng := NewEngine(cfg, driver)
- eng.SetChunkDuration(10 * time.Millisecond)
-
- ctx := context.Background()
- if err := eng.Start(ctx); err != nil {
- t.Fatalf("start: %v", err)
- }
- defer eng.Stop(ctx)
-
- time.Sleep(50 * time.Millisecond)
-
- // Update RDS text
- ps := "NEWPS"
- rt := "Now playing: test track"
- err := eng.UpdateConfig(LiveConfigUpdate{PS: &ps, RadioText: &rt})
- if err != nil {
- t.Fatalf("UpdateConfig RDS: %v", err)
- }
-
- time.Sleep(50 * time.Millisecond)
- stats := eng.Stats()
- if stats.Underruns > 0 {
- t.Fatalf("underruns after RDS update: %d", stats.Underruns)
- }
- }
-
- func TestEngineLiveUpdateValidation(t *testing.T) {
- cfg := cfgpkg.Default()
- driver := platform.NewSimulatedDriver(nil)
- eng := NewEngine(cfg, driver)
-
- // Out of range frequency
- badFreq := 200.0
- if err := eng.UpdateConfig(LiveConfigUpdate{FrequencyMHz: &badFreq}); err == nil {
- t.Fatal("expected validation error for bad frequency")
- }
-
- // Out of range drive
- badDrive := 10.0
- if err := eng.UpdateConfig(LiveConfigUpdate{OutputDrive: &badDrive}); err == nil {
- t.Fatal("expected validation error for bad drive")
- }
-
- // Valid update should succeed
- goodDrive := 1.0
- if err := eng.UpdateConfig(LiveConfigUpdate{OutputDrive: &goodDrive}); err != nil {
- t.Fatalf("expected valid update to succeed: %v", err)
- }
- }
|