//go:build windows package cat import "testing" // The reported symptom: with spot mirroring on, the same station appeared two // or three times on the panorama. // // Its cause is not in OpsLog's spot pipeline — one cluster line produces one // SendSpot. It is that a popular DX station IS spotted two or three times, by // different operators within the same minute, and no two of them agree on the // frequency to better than a few tens of hertz. The backend assumed ExpertSDR // replaced a spot bearing a callsign it already had; it keys on the frequency // too, so each of those became its own marker. func TestSameStationSpottedBySeveralOperatorsIsDrawnOnce(t *testing.T) { tci := &TCI{spotsEnabled: true} draw, del := tci.noteSpot("UN7GK", 14025000) if !draw || del { t.Fatalf("first spot: draw=%v delete=%v, want draw and nothing to delete", draw, del) } // The same station, two more spotters, a few tens of hertz apart. for _, hz := range []int64{14025120, 14024900} { if draw, del := tci.noteSpot("UN7GK", hz); draw || del { t.Errorf("re-spot at %d Hz: draw=%v delete=%v, want nothing sent — this is the duplicate marker", hz, draw, del) } } } // A station that really moves must still move on the panorama, and the marker // left where it was must go. Deleting first is the whole difference between // "the spot follows the station" and "the station collects markers". func TestAStationThatMovesReplacesItsMarker(t *testing.T) { tci := &TCI{spotsEnabled: true} tci.noteSpot("UN7GK", 14025000) draw, del := tci.noteSpot("UN7GK", 14032000) // 7 kHz up: a real QSY if !draw || !del { t.Fatalf("after a QSY: draw=%v delete=%v, want the old marker deleted and a new one drawn", draw, del) } // And the new position becomes the reference, so spotters agreeing with it // are quiet again. if draw, _ := tci.noteSpot("UN7GK", 14032100); draw { t.Error("a spot at the station's new frequency was drawn again") } } // Case matters nowhere in ham radio, and the cluster is not consistent about it. func TestSpotMemoryIgnoresCase(t *testing.T) { tci := &TCI{spotsEnabled: true} tci.noteSpot("un7gk", 14025000) if draw, _ := tci.noteSpot("UN7GK", 14025000); draw { t.Error("the same call in another case was treated as a different station") } } // Two different stations are two markers — the whole point of the panorama. func TestDifferentStationsEachGetAMarker(t *testing.T) { tci := &TCI{spotsEnabled: true} tci.noteSpot("UN7GK", 14025000) draw, del := tci.noteSpot("ZD7BG", 14025050) // 50 Hz away, a different operator if !draw { t.Error("a second station near the first was swallowed as a duplicate") } if del { t.Error("deleting by callsign would have removed a spot this station never had") } } // The connection drops and comes back: Connect sends spot_clear, so the // panorama is empty. If the memory survived that, the next spot for each of // those calls would be suppressed as "already drawn" onto an empty panorama — // the operator's spots would simply stop appearing until they changed // frequency. func TestReconnectingForgetsWhatWasDrawn(t *testing.T) { tci := &TCI{spotsEnabled: true} tci.noteSpot("UN7GK", 14025000) tci.mu.Lock() tci.spotFreq = map[string]int64{} // what Connect does alongside spot_clear tci.mu.Unlock() if draw, del := tci.noteSpot("UN7GK", 14025000); !draw || del { t.Errorf("after a reconnect: draw=%v delete=%v, want it drawn again and nothing deleted", draw, del) } }