Wideband autonomous SDR analysis engine forked from sdr-visual-suite
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

170 líneas
6.0KB

  1. package pipeline
  2. import "testing"
  3. func TestScheduleCandidates(t *testing.T) {
  4. policy := Policy{MaxRefinementJobs: 2, MinCandidateSNRDb: 5}
  5. cands := []Candidate{
  6. {ID: 1, CenterHz: 100, SNRDb: 4, BandwidthHz: 10000, PeakDb: 1},
  7. {ID: 2, CenterHz: 200, SNRDb: 12, BandwidthHz: 50000, PeakDb: 3},
  8. {ID: 3, CenterHz: 300, SNRDb: 10, BandwidthHz: 25000, PeakDb: 2},
  9. {ID: 4, CenterHz: 400, SNRDb: 20, BandwidthHz: 100000, PeakDb: 5},
  10. }
  11. got := ScheduleCandidates(cands, policy)
  12. if len(got) != 2 {
  13. t.Fatalf("expected 2 scheduled candidates, got %d", len(got))
  14. }
  15. if got[0].Candidate.ID != 4 {
  16. t.Fatalf("expected strongest candidate first, got id=%d", got[0].Candidate.ID)
  17. }
  18. if got[1].Candidate.ID != 2 {
  19. t.Fatalf("expected next strongest candidate second, got id=%d", got[1].Candidate.ID)
  20. }
  21. }
  22. func TestBuildRefinementPlanTracksDrops(t *testing.T) {
  23. policy := Policy{MaxRefinementJobs: 1, MinCandidateSNRDb: 10}
  24. cands := []Candidate{
  25. {ID: 1, CenterHz: 100, SNRDb: 4, BandwidthHz: 10000, PeakDb: 1},
  26. {ID: 2, CenterHz: 200, SNRDb: 12, BandwidthHz: 50000, PeakDb: 3},
  27. {ID: 3, CenterHz: 300, SNRDb: 11, BandwidthHz: 25000, PeakDb: 2},
  28. }
  29. plan := BuildRefinementPlan(cands, policy)
  30. if plan.TotalCandidates != 3 {
  31. t.Fatalf("expected total candidates 3, got %d", plan.TotalCandidates)
  32. }
  33. if plan.DroppedBySNR != 1 {
  34. t.Fatalf("expected 1 dropped by SNR, got %d", plan.DroppedBySNR)
  35. }
  36. if plan.DroppedByBudget != 1 {
  37. t.Fatalf("expected 1 dropped by budget, got %d", plan.DroppedByBudget)
  38. }
  39. if len(plan.Selected) != 1 || plan.Selected[0].Candidate.ID != 2 {
  40. t.Fatalf("unexpected plan selection: %+v", plan.Selected)
  41. }
  42. if len(plan.WorkItems) != len(cands) {
  43. t.Fatalf("expected work items for all candidates, got %d", len(plan.WorkItems))
  44. }
  45. item2 := findWorkItem(plan.WorkItems, 2)
  46. if item2 == nil || item2.Status != RefinementStatusSelected || item2.Reason != RefinementReasonSelected {
  47. t.Fatalf("expected candidate 2 selected with reason, got %+v", item2)
  48. }
  49. item1 := findWorkItem(plan.WorkItems, 1)
  50. if item1 == nil || item1.Reason != RefinementReasonBelowSNR {
  51. t.Fatalf("expected candidate 1 dropped by snr, got %+v", item1)
  52. }
  53. item3 := findWorkItem(plan.WorkItems, 3)
  54. if item3 == nil || item3.Reason != RefinementReasonBudget {
  55. t.Fatalf("expected candidate 3 dropped by budget, got %+v", item3)
  56. }
  57. }
  58. func TestBuildRefinementPlanRespectsMaxConcurrent(t *testing.T) {
  59. policy := Policy{MaxRefinementJobs: 5, RefinementMaxConcurrent: 2, MinCandidateSNRDb: 0}
  60. cands := []Candidate{
  61. {ID: 1, CenterHz: 100, SNRDb: 9},
  62. {ID: 2, CenterHz: 200, SNRDb: 8},
  63. {ID: 3, CenterHz: 300, SNRDb: 7},
  64. }
  65. plan := BuildRefinementPlan(cands, policy)
  66. if plan.Budget != 2 {
  67. t.Fatalf("expected budget 2, got %d", plan.Budget)
  68. }
  69. if len(plan.Selected) != 2 {
  70. t.Fatalf("expected 2 selected, got %d", len(plan.Selected))
  71. }
  72. }
  73. func TestBuildRefinementPlanAppliesMonitorSpan(t *testing.T) {
  74. policy := Policy{MaxRefinementJobs: 5, MinCandidateSNRDb: 0, MonitorStartHz: 150, MonitorEndHz: 350}
  75. cands := []Candidate{
  76. {ID: 1, CenterHz: 100, BandwidthHz: 20},
  77. {ID: 2, CenterHz: 200, BandwidthHz: 50},
  78. {ID: 3, CenterHz: 300, BandwidthHz: 100},
  79. {ID: 4, CenterHz: 500, BandwidthHz: 50},
  80. }
  81. plan := BuildRefinementPlan(cands, policy)
  82. if plan.DroppedByMonitor != 2 {
  83. t.Fatalf("expected 2 dropped by monitor, got %d", plan.DroppedByMonitor)
  84. }
  85. if len(plan.Selected) != 2 {
  86. t.Fatalf("expected 2 selected within monitor, got %d", len(plan.Selected))
  87. }
  88. }
  89. func TestBuildRefinementPlanAppliesMonitorSpanCentered(t *testing.T) {
  90. policy := Policy{MaxRefinementJobs: 5, MinCandidateSNRDb: 0, MonitorCenterHz: 300, MonitorSpanHz: 200}
  91. cands := []Candidate{
  92. {ID: 1, CenterHz: 100, BandwidthHz: 20},
  93. {ID: 2, CenterHz: 250, BandwidthHz: 50},
  94. {ID: 3, CenterHz: 300, BandwidthHz: 100},
  95. {ID: 4, CenterHz: 420, BandwidthHz: 50},
  96. }
  97. plan := BuildRefinementPlan(cands, policy)
  98. if plan.DroppedByMonitor != 1 {
  99. t.Fatalf("expected 1 dropped by monitor, got %d", plan.DroppedByMonitor)
  100. }
  101. if len(plan.Selected) != 3 {
  102. t.Fatalf("expected 3 selected within monitor, got %d", len(plan.Selected))
  103. }
  104. }
  105. func TestAutoSpanForHint(t *testing.T) {
  106. span, source := AutoSpanForHint("WFM_STEREO")
  107. if span < 150000 || source == "" {
  108. t.Fatalf("expected WFM span, got %.0f (%s)", span, source)
  109. }
  110. span, source = AutoSpanForHint("CW")
  111. if span != 500 || source == "" {
  112. t.Fatalf("expected CW span, got %.0f (%s)", span, source)
  113. }
  114. span, source = AutoSpanForHint("")
  115. if span != 0 || source != "" {
  116. t.Fatalf("expected empty span for unknown hint, got %.0f (%s)", span, source)
  117. }
  118. }
  119. func TestScheduleCandidatesPriorityBoost(t *testing.T) {
  120. policy := Policy{MaxRefinementJobs: 1, MinCandidateSNRDb: 0, SignalPriorities: []string{"digital"}}
  121. got := ScheduleCandidates([]Candidate{
  122. {ID: 1, SNRDb: 15, Hint: "voice"},
  123. {ID: 2, SNRDb: 14, Hint: "digital-burst"},
  124. }, policy)
  125. if len(got) != 1 || got[0].Candidate.ID != 2 {
  126. t.Fatalf("expected priority boost to favor digital candidate, got %+v", got)
  127. }
  128. }
  129. func TestBuildRefinementPlanPriorityStats(t *testing.T) {
  130. policy := Policy{MaxRefinementJobs: 1, MinCandidateSNRDb: 0}
  131. cands := []Candidate{
  132. {ID: 1, CenterHz: 100, SNRDb: 8, BandwidthHz: 10000, PeakDb: 2},
  133. {ID: 2, CenterHz: 200, SNRDb: 12, BandwidthHz: 20000, PeakDb: 4},
  134. }
  135. plan := BuildRefinementPlan(cands, policy)
  136. if plan.PriorityMax < plan.PriorityMin {
  137. t.Fatalf("priority bounds invalid: %+v", plan)
  138. }
  139. if len(plan.Selected) != 1 {
  140. t.Fatalf("expected 1 selected, got %d", len(plan.Selected))
  141. }
  142. if plan.PriorityCutoff != plan.Selected[0].Priority {
  143. t.Fatalf("expected cutoff to match selection, got %.2f vs %.2f", plan.PriorityCutoff, plan.Selected[0].Priority)
  144. }
  145. if plan.Selected[0].Breakdown == nil {
  146. t.Fatalf("expected breakdown on selected candidate")
  147. }
  148. if plan.Selected[0].Score == nil || plan.Selected[0].Score.Total == 0 {
  149. t.Fatalf("expected score on selected candidate")
  150. }
  151. }
  152. func findWorkItem(items []RefinementWorkItem, id int64) *RefinementWorkItem {
  153. for i := range items {
  154. if items[i].Candidate.ID == id {
  155. return &items[i]
  156. }
  157. }
  158. return nil
  159. }