Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
132 lines
3.6 KiB
Go
132 lines
3.6 KiB
Go
package wshub
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// newTestManager builds a hub + manager pair with no goroutines running.
|
|
func newTestManager(t *testing.T) (*Hub, *SourceManager, string) {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "sources.json")
|
|
h := NewHub()
|
|
sm := NewSourceManager(h, path)
|
|
h.SetSourceManager(sm)
|
|
return h, sm, path
|
|
}
|
|
|
|
func TestSaveWritesSourcesAndCalibration(t *testing.T) {
|
|
h, sm, path := newTestManager(t)
|
|
|
|
// Register two sources without starting any UDP client.
|
|
sm.mu.Lock()
|
|
sm.sources["s1"] = &managedSource{id: "s1", label: "wave", addr: "127.0.0.1:44500"}
|
|
sm.sources["s2"] = &managedSource{
|
|
id: "s2", label: "mc", addr: "127.0.0.1:44501",
|
|
multicastGroup: "239.0.0.1", dataPort: 44502,
|
|
}
|
|
sm.mu.Unlock()
|
|
|
|
if !h.cal.Set(CalConfig{Source: "wave", Signal: "Adc", Scale: 0.5, Offset: -1.25, Unit: "V"}) {
|
|
t.Fatal("calibration rejected")
|
|
}
|
|
if err := sm.Save(); err != nil {
|
|
t.Fatalf("Save: %v", err)
|
|
}
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile: %v", err)
|
|
}
|
|
srcs, cals, err := parseConfigFile(data)
|
|
if err != nil {
|
|
t.Fatalf("parseConfigFile: %v\n%s", err, data)
|
|
}
|
|
if len(srcs) != 2 {
|
|
t.Fatalf("got %d sources, want 2\n%s", len(srcs), data)
|
|
}
|
|
// Save sorts by label so the file is byte-stable across runs.
|
|
if srcs[0].Label != "mc" || srcs[1].Label != "wave" {
|
|
t.Errorf("source order = %q,%q, want mc,wave", srcs[0].Label, srcs[1].Label)
|
|
}
|
|
if len(cals) != 1 || cals[0].Signal != "Adc" || cals[0].Scale != 0.5 {
|
|
t.Fatalf("calibration round-trip failed: %+v\n%s", cals, data)
|
|
}
|
|
}
|
|
|
|
func TestSaveWithoutFilePathFails(t *testing.T) {
|
|
h := NewHub()
|
|
sm := NewSourceManager(h, "")
|
|
h.SetSourceManager(sm)
|
|
if err := sm.Save(); err == nil {
|
|
t.Error("Save() with no path = nil error, want error")
|
|
}
|
|
}
|
|
|
|
func TestLoadSeedsCalibrationTable(t *testing.T) {
|
|
h, sm, path := newTestManager(t)
|
|
// No "addr" blocks: Load must not start any UDP client during the test.
|
|
if err := os.WriteFile(path, []byte(`[
|
|
{"source":"wave","signal":"Adc","scale":0.25,"offset":2,"unit":"mV"},
|
|
{"source":"wave","signal":"Dac","scale":2}
|
|
]`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := sm.Load(path); err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
got := h.cal.List()
|
|
if len(got) != 2 {
|
|
t.Fatalf("List() = %d entries, want 2", len(got))
|
|
}
|
|
if got[0].Signal != "Adc" || got[0].Unit != "mV" || got[0].Offset != 2 {
|
|
t.Errorf("Adc = %+v", got[0])
|
|
}
|
|
if sm.Path() != path {
|
|
t.Errorf("Path() = %q, want %q", sm.Path(), path)
|
|
}
|
|
}
|
|
|
|
func TestReloadReplacesCalibrationAndKeepsLiveSources(t *testing.T) {
|
|
h, sm, path := newTestManager(t)
|
|
|
|
// A live source that the file does not mention must survive the reload.
|
|
sm.mu.Lock()
|
|
sm.sources["s1"] = &managedSource{id: "s1", label: "live", addr: "127.0.0.1:44999"}
|
|
sm.mu.Unlock()
|
|
|
|
// A stale calibration that the file does not mention must be dropped.
|
|
h.cal.Set(CalConfig{Source: "stale", Signal: "Old", Scale: 9})
|
|
|
|
if err := os.WriteFile(path, []byte(`[
|
|
{"source":"wave","signal":"Adc","scale":0.5}
|
|
]`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := sm.Reload(); err != nil {
|
|
t.Fatalf("Reload: %v", err)
|
|
}
|
|
|
|
got := h.cal.List()
|
|
if len(got) != 1 || got[0].Source != "wave" {
|
|
t.Fatalf("after Reload, calibration = %+v, want only wave/Adc", got)
|
|
}
|
|
sm.mu.RLock()
|
|
_, alive := sm.sources["s1"]
|
|
n := len(sm.sources)
|
|
sm.mu.RUnlock()
|
|
if !alive || n != 1 {
|
|
t.Errorf("live source count = %d (s1 alive=%v), want 1 / true", n, alive)
|
|
}
|
|
}
|
|
|
|
func TestReloadWithoutFilePathFails(t *testing.T) {
|
|
h := NewHub()
|
|
sm := NewSourceManager(h, "")
|
|
h.SetSourceManager(sm)
|
|
if err := sm.Reload(); err == nil {
|
|
t.Error("Reload() with no path = nil error, want error")
|
|
}
|
|
}
|