Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

252 lines
6.3KB

  1. package app
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. cfgpkg "github.com/jan/fm-rds-tx/internal/config"
  7. "github.com/jan/fm-rds-tx/internal/platform"
  8. )
  9. func TestEngineContinuousRun(t *testing.T) {
  10. cfg := cfgpkg.Default()
  11. driver := platform.NewSimulatedDriver(nil)
  12. eng := NewEngine(cfg, driver)
  13. eng.SetChunkDuration(10 * time.Millisecond)
  14. ctx := context.Background()
  15. if err := eng.Start(ctx); err != nil {
  16. t.Fatalf("start: %v", err)
  17. }
  18. // Let it run for 200ms
  19. time.Sleep(200 * time.Millisecond)
  20. stats := eng.Stats()
  21. if stats.State != "running" {
  22. t.Fatalf("expected running, got %s", stats.State)
  23. }
  24. if stats.ChunksProduced < 5 {
  25. t.Fatalf("expected at least 5 chunks, got %d", stats.ChunksProduced)
  26. }
  27. if stats.TotalSamples == 0 {
  28. t.Fatal("expected non-zero samples")
  29. }
  30. if err := eng.Stop(ctx); err != nil {
  31. t.Fatalf("stop: %v", err)
  32. }
  33. stats = eng.Stats()
  34. if stats.State != "idle" {
  35. t.Fatalf("expected idle after stop, got %s", stats.State)
  36. }
  37. }
  38. func TestEngineDoubleStartFails(t *testing.T) {
  39. cfg := cfgpkg.Default()
  40. driver := platform.NewSimulatedDriver(nil)
  41. eng := NewEngine(cfg, driver)
  42. ctx := context.Background()
  43. if err := eng.Start(ctx); err != nil {
  44. t.Fatalf("first start: %v", err)
  45. }
  46. defer eng.Stop(ctx)
  47. if err := eng.Start(ctx); err == nil {
  48. t.Fatal("expected error on double start")
  49. }
  50. }
  51. func TestEngineDriverStats(t *testing.T) {
  52. cfg := cfgpkg.Default()
  53. driver := platform.NewSimulatedDriver(nil)
  54. eng := NewEngine(cfg, driver)
  55. eng.SetChunkDuration(10 * time.Millisecond)
  56. ctx := context.Background()
  57. _ = eng.Start(ctx)
  58. time.Sleep(100 * time.Millisecond)
  59. _ = eng.Stop(ctx)
  60. driverStats := driver.Stats()
  61. if driverStats.SamplesWritten == 0 {
  62. t.Fatal("expected driver to have written samples")
  63. }
  64. if driverStats.FramesWritten == 0 {
  65. t.Fatal("expected driver to have written frames")
  66. }
  67. }
  68. func TestEngineSplitRate(t *testing.T) {
  69. // Simulate Pluto-like config: composite at 228kHz, device at 2.28MHz
  70. cfg := cfgpkg.Default()
  71. cfg.Backend.DeviceSampleRateHz = 2280000 // 10:1 ratio
  72. driver := platform.NewSimulatedDriver(nil)
  73. eng := NewEngine(cfg, driver)
  74. eng.SetChunkDuration(10 * time.Millisecond)
  75. // Verify split-rate path was chosen
  76. if eng.upsampler == nil {
  77. t.Fatal("expected split-rate mode (upsampler != nil)")
  78. }
  79. ctx := context.Background()
  80. if err := eng.Start(ctx); err != nil {
  81. t.Fatalf("start: %v", err)
  82. }
  83. time.Sleep(200 * time.Millisecond)
  84. stats := eng.Stats()
  85. if stats.ChunksProduced < 5 {
  86. t.Fatalf("expected at least 5 chunks, got %d", stats.ChunksProduced)
  87. }
  88. // With 10:1 upsampling and 10ms chunks at 228kHz source:
  89. // 2280 src samples → 22800 output samples per chunk
  90. // 5 chunks minimum → at least 114000 samples
  91. if stats.TotalSamples < 50000 {
  92. t.Fatalf("expected at least 50000 upsampled samples, got %d", stats.TotalSamples)
  93. }
  94. if err := eng.Stop(ctx); err != nil {
  95. t.Fatalf("stop: %v", err)
  96. }
  97. if stats.Underruns > 0 {
  98. t.Fatalf("unexpected underruns: %d", stats.Underruns)
  99. }
  100. }
  101. func TestEngineSameRate(t *testing.T) {
  102. // Default config: deviceSampleRateHz=0, compositeRateHz=228000
  103. // EffectiveDeviceRate = 228000 → same-rate mode, no upsampler
  104. cfg := cfgpkg.Default()
  105. driver := platform.NewSimulatedDriver(nil)
  106. eng := NewEngine(cfg, driver)
  107. if eng.upsampler != nil {
  108. t.Fatal("expected same-rate mode (upsampler == nil)")
  109. }
  110. }
  111. func TestEngineLiveUpdateDSP(t *testing.T) {
  112. cfg := cfgpkg.Default()
  113. driver := platform.NewSimulatedDriver(nil)
  114. eng := NewEngine(cfg, driver)
  115. eng.SetChunkDuration(10 * time.Millisecond)
  116. ctx := context.Background()
  117. if err := eng.Start(ctx); err != nil {
  118. t.Fatalf("start: %v", err)
  119. }
  120. defer eng.Stop(ctx)
  121. time.Sleep(50 * time.Millisecond)
  122. // Update DSP params while running
  123. drive := 1.5
  124. stereo := false
  125. err := eng.UpdateConfig(LiveConfigUpdate{
  126. OutputDrive: &drive,
  127. StereoEnabled: &stereo,
  128. })
  129. if err != nil {
  130. t.Fatalf("UpdateConfig: %v", err)
  131. }
  132. // Engine should still be running after update
  133. time.Sleep(50 * time.Millisecond)
  134. stats := eng.Stats()
  135. if stats.State != "running" {
  136. t.Fatalf("expected running after update, got %s", stats.State)
  137. }
  138. if stats.Underruns > 0 {
  139. t.Fatalf("unexpected underruns after update: %d", stats.Underruns)
  140. }
  141. }
  142. func TestEngineLiveUpdateFrequency(t *testing.T) {
  143. cfg := cfgpkg.Default()
  144. driver := platform.NewSimulatedDriver(nil)
  145. eng := NewEngine(cfg, driver)
  146. eng.SetChunkDuration(10 * time.Millisecond)
  147. ctx := context.Background()
  148. if err := eng.Start(ctx); err != nil {
  149. t.Fatalf("start: %v", err)
  150. }
  151. defer eng.Stop(ctx)
  152. time.Sleep(50 * time.Millisecond)
  153. // Tune frequency
  154. freq := 99.5
  155. err := eng.UpdateConfig(LiveConfigUpdate{FrequencyMHz: &freq})
  156. if err != nil {
  157. t.Fatalf("UpdateConfig freq: %v", err)
  158. }
  159. // Let it process for a bit so the pending freq gets applied
  160. time.Sleep(50 * time.Millisecond)
  161. stats := eng.Stats()
  162. if stats.State != "running" {
  163. t.Fatalf("expected running after tune, got %s", stats.State)
  164. }
  165. }
  166. func TestEngineLiveUpdateRDS(t *testing.T) {
  167. cfg := cfgpkg.Default()
  168. driver := platform.NewSimulatedDriver(nil)
  169. eng := NewEngine(cfg, driver)
  170. eng.SetChunkDuration(10 * time.Millisecond)
  171. ctx := context.Background()
  172. if err := eng.Start(ctx); err != nil {
  173. t.Fatalf("start: %v", err)
  174. }
  175. defer eng.Stop(ctx)
  176. time.Sleep(50 * time.Millisecond)
  177. // Update RDS text
  178. ps := "NEWPS"
  179. rt := "Now playing: test track"
  180. err := eng.UpdateConfig(LiveConfigUpdate{PS: &ps, RadioText: &rt})
  181. if err != nil {
  182. t.Fatalf("UpdateConfig RDS: %v", err)
  183. }
  184. time.Sleep(50 * time.Millisecond)
  185. stats := eng.Stats()
  186. if stats.Underruns > 0 {
  187. t.Fatalf("underruns after RDS update: %d", stats.Underruns)
  188. }
  189. }
  190. func TestEngineLiveUpdateValidation(t *testing.T) {
  191. cfg := cfgpkg.Default()
  192. driver := platform.NewSimulatedDriver(nil)
  193. eng := NewEngine(cfg, driver)
  194. // Out of range frequency
  195. badFreq := 200.0
  196. if err := eng.UpdateConfig(LiveConfigUpdate{FrequencyMHz: &badFreq}); err == nil {
  197. t.Fatal("expected validation error for bad frequency")
  198. }
  199. // Out of range drive
  200. badDrive := 10.0
  201. if err := eng.UpdateConfig(LiveConfigUpdate{OutputDrive: &badDrive}); err == nil {
  202. t.Fatal("expected validation error for bad drive")
  203. }
  204. // Valid update should succeed
  205. goodDrive := 1.0
  206. if err := eng.UpdateConfig(LiveConfigUpdate{OutputDrive: &goodDrive}); err != nil {
  207. t.Fatalf("expected valid update to succeed: %v", err)
  208. }
  209. }