Files
OpsLog/profilereload_test.go
T
2026-09-05 19:07:21 +02:00

74 lines
2.8 KiB
Go

package main
import (
"os"
"regexp"
"strings"
"testing"
)
// Every setting in OpsLog is per profile, station hardware included. A device
// started at boot and never again therefore stays on the PREVIOUS profile's
// port until Settings is opened and saved — which is how an operator running an
// SPE on COM9 for HF and another on COM10 for 6 m, one per profile, found the
// amplifier still on the old port after switching. Save is not a connect
// button.
//
// This test keeps startup and reloadAfterProfileSwitch in lockstep: anything
// started at boot must either be re-applied on a profile switch or be listed
// below with the reason it must not be. Adding a device makes the choice
// explicit instead of leaving the fifth one to be found by a user.
func TestProfileSwitchReappliesEveryStartupDevice(t *testing.T) {
src, err := os.ReadFile("app.go")
if err != nil {
t.Fatalf("read app.go: %v", err)
}
// Started at boot but deliberately NOT re-run on a profile switch:
notPerProfile := map[string]string{
"startAllEnabledClusters": "the cluster panel reconnects itself; its servers are a global list",
"startGridCache": "a shared on-disk grid cache, not a profile's",
"startBandOpenFeed": "PSK Reporter, keyed on the operator grid it re-reads itself",
// The auto-call loop is ONE goroutine for the life of the process —
// starting a second on every profile switch would give one engine two
// sweepers. Its settings do follow the profile: reloadAfterProfileSwitch
// calls applyAutoCall, which re-reads them and clears the target.
"startAutoCall": "a single sweeper goroutine; applyAutoCall in the reload carries the settings",
}
startup := body(t, string(src), "func (a *App) startup(ctx context.Context) {")
reload := body(t, string(src), "func (a *App) reloadAfterProfileSwitch() {")
call := regexp.MustCompile(`a\.(start[A-Z][A-Za-z]*)\b`)
seen := map[string]bool{}
for _, m := range call.FindAllStringSubmatch(startup, -1) {
name := m[1]
if seen[name] || notPerProfile[name] != "" {
continue
}
seen[name] = true
if !strings.Contains(reload, "a."+name) {
t.Errorf("%s runs at startup but not on a profile switch — the device stays on the previous profile's settings.\n"+
"Add it to reloadAfterProfileSwitch, or to notPerProfile here with the reason it must not follow the profile.", name)
}
}
if len(seen) == 0 {
t.Fatal("no startup device starters found — this test has stopped checking anything")
}
}
// body returns the source of the function opening with the given signature,
// up to the closing brace in column 0.
func body(t *testing.T, src, signature string) string {
t.Helper()
i := strings.Index(src, signature)
if i < 0 {
t.Fatalf("%q not found in app.go", signature)
}
rest := src[i+len(signature):]
if j := strings.Index(rest, "\n}"); j >= 0 {
return rest[:j]
}
return rest
}