🎉 live server seems to be working now
This commit is contained in:
198
third_party/sendspin-go/pkg/sync/clock_test.go
vendored
Normal file
198
third_party/sendspin-go/pkg/sync/clock_test.go
vendored
Normal 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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user