package main import ( "os" "strings" "testing" ) // The tracking mode decides how often a motorized antenna's elements run, so a // value that fails to parse must not silently become the most aggressive // setting. Anything unrecognised — including the empty string every config // written before this option existed contains — has to land on the threshold // mode, which is exactly what those configs already did. func TestNormMotorTrackMode(t *testing.T) { for _, tc := range []struct{ in, want string }{ {"always", motorTrackAlways}, {"ALWAYS", motorTrackAlways}, {" band ", motorTrackBand}, {"step", motorTrackStep}, {"", motorTrackStep}, // never configured — behaves as before {"everytime", motorTrackStep}, // near-miss, not "always" {"per-band", motorTrackStep}, // near-miss, not "band" {"25", motorTrackStep}, // a step value fed in by mistake } { if got := normMotorTrackMode(tc.in); got != tc.want { t.Errorf("normMotorTrackMode(%q) = %q, want %q", tc.in, got, tc.want) } } } // Band mode compares the band the rig is on against the band the antenna was // last commanded for. That comparison is bandForHz, and the property it has to // have is that two frequencies far apart within one band agree while two // frequencies close together across a band edge do not — otherwise the antenna // either never moves or moves on every QSY. func TestBandForHzDrivesBandTracking(t *testing.T) { same := [][2]int64{ {14000000, 14350000}, // both ends of 20 m — one band, no move {7000000, 7200000}, // 40 m {50000000, 52000000}, // 6 m, a wide one } for _, p := range same { if a, b := bandForHz(p[0]), bandForHz(p[1]); a != b || a == "" { t.Errorf("%.3f MHz is %q but %.3f MHz is %q — band mode would re-tune inside one band", float64(p[0])/1e6, a, float64(p[1])/1e6, b) } } // A small QSY that crosses from 30 m into 20 m has to read as a band change. if a, b := bandForHz(10150000), bandForHz(14000000); a == b { t.Errorf("30 m and 20 m both read %q — band mode would never re-tune between them", a) } } // A "Test connection" on a serial port must not open a port OpsLog already // holds: a COM port has one owner, and the owner is the running antenna client. // Building a second client made the two fight — one of them logging "Serial port // busy" for the rest of the session. func TestTestUltrabeamReusesTheLiveSerialClient(t *testing.T) { src, err := os.ReadFile("app.go") if err != nil { t.Fatal(err) } body := funcBody(t, string(src), "func (a *App) TestUltrabeam(s UltrabeamSettings) error {") if !strings.Contains(body, "a.liveMotorFor(s)") { t.Error("TestUltrabeam builds a client without first checking whether the live one already owns that port") } // The check has to come BEFORE the second client is created, or it is no check. if i, j := strings.Index(body, "a.liveMotorFor(s)"), strings.Index(body, "newMotorClient(s)"); i < 0 || j < 0 || i > j { t.Error("the live-client check must precede newMotorClient") } } // funcBody returns the source of a function, up to the closing brace in column 0. func funcBody(t *testing.T, src, signature string) string { t.Helper() i := strings.Index(src, signature) if i < 0 { t.Fatalf("function not found: %s", signature) } rest := src[i:] if j := strings.Index(rest, "\n}\n"); j >= 0 { return rest[:j] } return rest }