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", } 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 }