74 lines
2.8 KiB
Go
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
|
|
}
|