🎉 live server seems to be working now

This commit is contained in:
2026-05-14 14:29:57 +02:00
commit d72e439fd9
181 changed files with 47406 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
// ABOUTME: Tests for Kalman-filter-based clock synchronization
// ABOUTME: Tests RTT calculation, time conversion, quality tracking
package sync
import (
"testing"
"time"
)
func TestRTTCalculation(t *testing.T) {
t1 := int64(1000000)
t2 := int64(2000)
t3 := int64(2500)
t4 := int64(1005000)
cs := NewClockSync()
cs.ProcessSyncResponse(t1, t2, t3, t4)
// RTT = (t4-t1) - (t3-t2) = 5000 - 500 = 4500µs
rtt, _ := cs.GetStats()
if rtt != 4500 {
t.Errorf("expected RTT 4500µs, got %dµs", rtt)
}
}
func TestSyncEstablishment(t *testing.T) {
cs := NewClockSync()
if cs.filter.Synced() {
t.Error("expected not synced initially")
}
// One low-noise sample is enough to mark the filter Synced.
cs.ProcessSyncResponse(1_000_000, 500_000, 500_100, 1_000_200)
if !cs.filter.Synced() {
t.Error("expected synced after first response")
}
// Drive enough low-noise samples to converge to QualityGood.
for i := 1; i < 60; i++ {
t1 := int64(1_000_000 + i*100_000)
cs.ProcessSyncResponse(t1, t1+50, t1+150, t1+200) // ~200µs RTT
}
_, quality := cs.GetStats()
if quality != QualityGood {
t.Errorf("expected QualityGood after convergence, got %v", quality)
}
}
func TestServerToLocalTimeConversion(t *testing.T) {
cs := NewClockSync()
clientNow := time.Now().UnixMicro()
serverTime := int64(5000000) // 5s into server loop
// Feed several samples to let the filter converge
for i := 0; i < 10; i++ {
ct := clientNow + int64(i*100000) // 100ms apart
st := serverTime + int64(i*100000)
cs.ProcessSyncResponse(ct-1000, st, st+50, ct)
}
// Convert a server time 100ms in the future
futureServer := serverTime + 10*100000 + 100000
localTime := cs.ServerToLocalTime(futureServer)
expectedLocal := time.UnixMicro(clientNow + 10*100000 + 100000)
diff := localTime.Sub(expectedLocal).Microseconds()
if diff < -50000 || diff > 50000 {
t.Errorf("time conversion off by %dµs", diff)
}
}
func TestQualityTracking(t *testing.T) {
cs := NewClockSync()
// Single noisy sample → high σ → not yet QualityGood.
cs.ProcessSyncResponse(1000000, 1000, 1100, 1025000)
_, quality := cs.GetStats()
if quality == QualityGood {
t.Errorf("expected non-Good quality on first sample, got %v", quality)
}
// Drive enough low-noise samples to converge below the QualityGood threshold.
for i := 1; i < 60; i++ {
t1 := int64(1_000_000 + i*100_000)
cs.ProcessSyncResponse(t1, t1+50, t1+150, t1+200) // ~200µs RTT
}
_, quality = cs.GetStats()
if quality != QualityGood {
t.Errorf("expected QualityGood after convergence, got %v (filter err=%d)",
quality, cs.filter.GetError())
}
}
func TestQualityDegradation(t *testing.T) {
cs := NewClockSync()
// Drive enough low-noise samples to reach QualityGood.
for i := 0; i < 60; i++ {
t1 := int64(1_000_000 + i*100_000)
cs.ProcessSyncResponse(t1, t1+50, t1+150, t1+200) // ~200µs RTT
}
quality := cs.CheckQuality()
if quality != QualityGood {
t.Errorf("expected QualityGood initially, got %v", quality)
}
cs.mu.Lock()
cs.lastSync = time.Now().Add(-6 * time.Second)
cs.mu.Unlock()
quality = cs.CheckQuality()
if quality != QualityLost {
t.Errorf("expected QualityLost after 6s, got %v", quality)
}
}
func TestClockSync_ServerMicrosNow(t *testing.T) {
cs := NewClockSync()
// Before sync, should return roughly current Unix micros
now1 := cs.ServerMicrosNow()
unixNow := time.Now().UnixMicro()
if abs64(now1-unixNow) > 1000000 {
t.Errorf("before sync: expected ~%d, got %d", unixNow, now1)
}
// After sync, should return server-frame time
cs.ProcessSyncResponse(1000, 500000, 500100, 1200)
now2 := cs.ServerMicrosNow()
if now2 == 0 {
t.Error("after sync: got zero")
}
}
func TestNewClockSyncWithConfig(t *testing.T) {
cfg := DefaultTimeFilterConfig()
cfg.MaxErrorScale = 0.25
csDefault := NewClockSync()
csScaled := NewClockSyncWithConfig(cfg)
const samples = 30
for i := 0; i < samples; i++ {
t1 := int64(1_000_000 + i*100_000)
t2 := int64(500_000 + i*100_000)
t3 := t2 + 100
t4 := t1 + 1000 // ~1ms RTT
csDefault.ProcessSyncResponse(t1, t2, t3, t4)
csScaled.ProcessSyncResponse(t1, t2, t3, t4)
}
errDefault := csDefault.filter.GetError()
errScaled := csScaled.filter.GetError()
if !(errScaled < errDefault) {
t.Errorf("expected scaled (0.25) error < default (0.5); got scaled=%d default=%d",
errScaled, errDefault)
}
}
func TestConcurrentAccess(t *testing.T) {
cs := NewClockSync()
cs.ProcessSyncResponse(1000000, 1000, 1100, 1025000)
done := make(chan bool, 10)
for i := 0; i < 10; i++ {
go func() {
for j := 0; j < 100; j++ {
cs.GetStats()
cs.CheckQuality()
cs.ServerMicrosNow()
cs.ServerToLocalTime(int64(j * 1000))
cs.ProcessSyncResponse(
int64(1000000+j), int64(1000+j),
int64(1100+j), int64(1025000+j),
)
}
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
rtt, quality := cs.GetStats()
if rtt <= 0 {
t.Error("invalid RTT after concurrent access")
}
if quality == QualityLost {
t.Error("unexpected QualityLost after concurrent access")
}
}