mirror of
https://github.com/renorris/openfsd
synced 2026-03-22 06:25:35 +08:00
331 lines
8.5 KiB
Go
331 lines
8.5 KiB
Go
package fsd
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
)
|
|
|
|
// TestRegister tests the registration of clients with unique and duplicate callsigns.
|
|
func TestRegister(t *testing.T) {
|
|
p := newPostOffice()
|
|
client1 := &Client{loginData: loginData{callsign: "client1"}}
|
|
client1.lat.Store(0)
|
|
client1.lon.Store(0)
|
|
client1.visRange.Store(100000)
|
|
err := p.register(client1)
|
|
if err != nil {
|
|
t.Errorf("expected no error, got %v", err)
|
|
}
|
|
if p.clientMap["client1"] != client1 {
|
|
t.Errorf("expected client1 in map")
|
|
}
|
|
client2 := &Client{loginData: loginData{callsign: "client1"}}
|
|
client2.lat.Store(0)
|
|
client2.lon.Store(0)
|
|
client2.visRange.Store(100000)
|
|
err = p.register(client2)
|
|
if err != ErrCallsignInUse {
|
|
t.Errorf("expected ErrCallsignInUse, got %v", err)
|
|
}
|
|
if p.clientMap["client1"] != client1 {
|
|
t.Errorf("expected original client1 in map")
|
|
}
|
|
}
|
|
|
|
// TestRelease tests the removal of a client and its effect on search results.
|
|
func TestRelease(t *testing.T) {
|
|
p := newPostOffice()
|
|
client1 := &Client{loginData: loginData{callsign: "client1"}}
|
|
client1.lat.Store(0)
|
|
client1.lon.Store(0)
|
|
client1.visRange.Store(100000)
|
|
err := p.register(client1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client2 := &Client{loginData: loginData{callsign: "client2"}}
|
|
client2.lat.Store(0)
|
|
client2.lon.Store(0)
|
|
client2.visRange.Store(200000)
|
|
err = p.register(client2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var found []*Client
|
|
p.search(client2, func(recipient *Client) bool {
|
|
found = append(found, recipient)
|
|
return true
|
|
})
|
|
if len(found) != 1 || found[0] != client1 {
|
|
t.Errorf("expected to find client1, got %v", found)
|
|
}
|
|
|
|
p.release(client1)
|
|
_, exists := p.clientMap["client1"]
|
|
if exists {
|
|
t.Errorf("expected client1 to be removed from map")
|
|
}
|
|
|
|
found = nil
|
|
p.search(client2, func(recipient *Client) bool {
|
|
found = append(found, recipient)
|
|
return true
|
|
})
|
|
if len(found) != 0 {
|
|
t.Errorf("expected no clients found after release, got %v", found)
|
|
}
|
|
}
|
|
|
|
// TestUpdatePosition tests updating a client's position and its effect on search.
|
|
func TestUpdatePosition(t *testing.T) {
|
|
p := newPostOffice()
|
|
client1 := &Client{loginData: loginData{callsign: "client1"}}
|
|
client1.lat.Store(0)
|
|
client1.lon.Store(0)
|
|
client1.visRange.Store(100000)
|
|
err := p.register(client1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client2 := &Client{loginData: loginData{callsign: "client2"}}
|
|
client2.lat.Store(0.5)
|
|
client2.lon.Store(0.5)
|
|
client2.visRange.Store(100000)
|
|
err = p.register(client2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var found []*Client
|
|
p.search(client1, func(recipient *Client) bool {
|
|
found = append(found, recipient)
|
|
return true
|
|
})
|
|
if len(found) != 1 || found[0] != client2 {
|
|
t.Errorf("expected to find client2, got %v", found)
|
|
}
|
|
|
|
// Assuming updatePosition now takes lat, lon, visRange separately
|
|
newLat := 100.0
|
|
newLon := 100.0
|
|
newVisRange := 100000.0
|
|
p.updatePosition(client2, [2]float64{newLat, newLon}, newVisRange)
|
|
|
|
found = nil
|
|
p.search(client1, func(recipient *Client) bool {
|
|
found = append(found, recipient)
|
|
return true
|
|
})
|
|
if len(found) != 0 {
|
|
t.Errorf("expected no clients found after position update, got %v", found)
|
|
}
|
|
}
|
|
|
|
// TestSearch tests the search functionality with multiple clients.
|
|
func TestSearch(t *testing.T) {
|
|
p := newPostOffice()
|
|
client1 := &Client{loginData: loginData{callsign: "client1"}}
|
|
client1.lat.Store(32.0)
|
|
client1.lon.Store(-117.0)
|
|
client1.visRange.Store(100000)
|
|
err := p.register(client1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client2 := &Client{loginData: loginData{callsign: "client2"}}
|
|
client2.lat.Store(33.0)
|
|
client2.lon.Store(-117.0)
|
|
client2.visRange.Store(50000)
|
|
err = p.register(client2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client3 := &Client{loginData: loginData{callsign: "client3"}}
|
|
client3.lat.Store(34.0)
|
|
client3.lon.Store(-117.0)
|
|
client3.visRange.Store(50000)
|
|
err = p.register(client3)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var found []*Client
|
|
p.search(client1, func(recipient *Client) bool {
|
|
found = append(found, recipient)
|
|
return true
|
|
})
|
|
if len(found) != 1 || found[0].callsign != "client2" {
|
|
t.Errorf("expected to find client2, got %v", found)
|
|
}
|
|
|
|
found = nil
|
|
p.search(client2, func(recipient *Client) bool {
|
|
found = append(found, recipient)
|
|
return true
|
|
})
|
|
if len(found) != 1 || found[0].callsign != "client1" {
|
|
t.Errorf("expected to find client1, got %v", found)
|
|
}
|
|
|
|
found = nil
|
|
p.search(client3, func(recipient *Client) bool {
|
|
found = append(found, recipient)
|
|
return true
|
|
})
|
|
if len(found) != 0 {
|
|
t.Errorf("expected no clients found, got %v", found)
|
|
}
|
|
|
|
client4 := &Client{loginData: loginData{callsign: "client4"}}
|
|
client4.lat.Store(31.0)
|
|
client4.lon.Store(-117.0)
|
|
client4.visRange.Store(50000)
|
|
err = p.register(client4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
found = nil
|
|
p.search(client1, func(recipient *Client) bool {
|
|
found = append(found, recipient)
|
|
return true
|
|
})
|
|
foundCallsigns := make([]string, len(found))
|
|
for i, c := range found {
|
|
foundCallsigns[i] = c.callsign
|
|
}
|
|
sort.Strings(foundCallsigns)
|
|
expected := []string{"client2", "client4"}
|
|
sort.Strings(expected)
|
|
if !reflect.DeepEqual(foundCallsigns, expected) {
|
|
t.Errorf("expected %v, got %v", expected, foundCallsigns)
|
|
}
|
|
|
|
for _, c := range found {
|
|
if c == client1 {
|
|
t.Errorf("search included self")
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCalculateBoundingBox remains unchanged as it doesn't involve Client.
|
|
func TestCalculateBoundingBox(t *testing.T) {
|
|
const earthRadius = 6371000.0
|
|
tests := []struct {
|
|
name string
|
|
center [2]float64
|
|
radius float64
|
|
expectedMin [2]float64
|
|
expectedMax [2]float64
|
|
}{
|
|
{
|
|
name: "equator",
|
|
center: [2]float64{0, 0},
|
|
radius: 100000,
|
|
expectedMin: [2]float64{-0.8993216059187304, -0.8993216059187304},
|
|
expectedMax: [2]float64{0.8993216059187304, 0.8993216059187304},
|
|
},
|
|
{
|
|
name: "45 degrees latitude",
|
|
center: [2]float64{45, 0},
|
|
radius: 100000,
|
|
expectedMin: [2]float64{44.10067839408127, -1.2718328120254205},
|
|
expectedMax: [2]float64{45.89932160591873, 1.2718328120254205},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
min, max := calculateBoundingBox(tt.center, tt.radius)
|
|
if !approxEqual(min[0], tt.expectedMin[0]) || !approxEqual(min[1], tt.expectedMin[1]) {
|
|
t.Errorf("min mismatch: got %v, expected %v", min, tt.expectedMin)
|
|
}
|
|
if !approxEqual(max[0], tt.expectedMax[0]) || !approxEqual(max[1], tt.expectedMax[1]) {
|
|
t.Errorf("max mismatch: got %v, expected %v", max, tt.expectedMax)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func approxEqual(a, b float64) bool {
|
|
const epsilon = 1e-6
|
|
return math.Abs(a-b) < epsilon
|
|
}
|
|
|
|
// BenchmarkDistance remains unchanged as it doesn't involve Client directly.
|
|
func BenchmarkDistance(b *testing.B) {
|
|
const numPairs = 1024 * 64
|
|
lats1 := make([]float64, numPairs)
|
|
lons1 := make([]float64, numPairs)
|
|
lats2 := make([]float64, numPairs)
|
|
lons2 := make([]float64, numPairs)
|
|
|
|
// Seed for reproducible results
|
|
rand.Seed(42)
|
|
|
|
// Pre-generate random latitude and longitude pairs
|
|
for i := 0; i < numPairs; i++ {
|
|
lats1[i] = -90 + rand.Float64()*180 // Latitude: -90 to 90
|
|
lons1[i] = -180 + rand.Float64()*360 // Longitude: -180 to 180
|
|
lats2[i] = -90 + rand.Float64()*180 // Latitude: -90 to 90
|
|
lons2[i] = -180 + rand.Float64()*360 // Longitude: -180 to 180
|
|
}
|
|
|
|
// Reset timer to exclude setup time from measurement
|
|
b.ResetTimer()
|
|
|
|
// Run the benchmark loop
|
|
for i := 0; i < b.N; i++ {
|
|
idx := i % numPairs
|
|
_ = distance(lats1[idx], lons1[idx], lats2[idx], lons2[idx])
|
|
}
|
|
}
|
|
|
|
// benchmarkSearchWithN benchmarks search performance with n clients.
|
|
func benchmarkSearchWithN(b *testing.B, n int) {
|
|
// Create postOffice
|
|
p := newPostOffice()
|
|
|
|
// Create n clients
|
|
clients := make([]*Client, n)
|
|
for i := 0; i < n; i++ {
|
|
clients[i] = &Client{loginData: loginData{callsign: fmt.Sprintf("Client%d", i)}}
|
|
clients[i].lat.Store(-90 + rand.Float64()*180) // Latitude: -90 to 90
|
|
clients[i].lon.Store(-180 + rand.Float64()*360) // Longitude: -180 to 180
|
|
clients[i].visRange.Store(10000)
|
|
p.register(clients[i])
|
|
}
|
|
|
|
// Define callback
|
|
callback := func(recipient *Client) bool {
|
|
return true
|
|
}
|
|
|
|
// Report allocations
|
|
b.ReportAllocs()
|
|
|
|
// Reset timer
|
|
b.ResetTimer()
|
|
|
|
// Run the benchmark loop
|
|
for i := 0; i < b.N; i++ {
|
|
searchClient := clients[i%10]
|
|
p.search(searchClient, callback)
|
|
}
|
|
}
|
|
|
|
// BenchmarkSearch runs benchmarks for different client counts.
|
|
func BenchmarkSearch(b *testing.B) {
|
|
rand.Seed(42)
|
|
for _, n := range []int{100, 1000, 10000} {
|
|
b.Run(fmt.Sprintf("n=%d", n), func(b *testing.B) {
|
|
benchmarkSearchWithN(b, n)
|
|
})
|
|
}
|
|
}
|