package datachannel import ( "testing" "time" "github.com/stretchr/testify/require" ) var busyModes = []BitrateMode{BitrateModeBusyOnly, BitrateModeExcludeIdleDrain} // 1 KB written every second, each fully drained before the next write: never // backlogged, so there is no estimate. func TestBitrateCalculatorSparseFastAck(t *testing.T) { for _, mode := range busyModes { t.Run(mode.String(), func(t *testing.T) { c := NewBitrateCalculator(BitrateDuration, BitrateWindow, mode) require.NotNil(t, c) t0 := time.Now() for i := 0; i < 5; i++ { ts := t0.Add(time.Duration(i) * time.Second) c.AddBytes(1000, 1000, ts) _, ok := c.Bitrate(ts) require.False(t, ok) } }) } } // Standing backlog of 1000 bytes; every 100ms write 800 and 800 drain, sampled // buffer 1000+800=1800. 800 B / 100ms = 64000 bps. func TestBitrateCalculatorSustainedBacklog(t *testing.T) { for _, mode := range busyModes { t.Run(mode.String(), func(t *testing.T) { c := NewBitrateCalculator(BitrateDuration, BitrateWindow, mode) t0 := time.Now() c.AddBytes(800, 1800, t0) for i := 1; i <= 30; i++ { c.AddBytes(800, 1800, t0.Add(time.Duration(i)*100*time.Millisecond)) } bitrate, ok := c.Bitrate(t0.Add(3 * time.Second)) require.True(t, ok) require.InDelta(t, 64000, bitrate, 2000) }) } } // Buffer only climbs and nothing drains (stalled connection): the estimate // converges to ~0. func TestBitrateCalculatorStalledConnection(t *testing.T) { for _, mode := range busyModes { t.Run(mode.String(), func(t *testing.T) { c := NewBitrateCalculator(BitrateDuration, BitrateWindow, mode) t0 := time.Now() buffered := 0 for i := 0; i < 20; i++ { buffered += 1000 // wrote 1000, nothing drained c.AddBytes(1000, buffered, t0.Add(time.Duration(i)*50*time.Millisecond)) } bitrate, ok := c.Bitrate(t0.Add(time.Second)) require.True(t, ok) require.Less(t, bitrate, 1000) }) } } // A single write that drains to empty is not backlogged at the next sample, so // no estimate is produced. func TestBitrateCalculatorFlushNotCounted(t *testing.T) { for _, mode := range busyModes { t.Run(mode.String(), func(t *testing.T) { c := NewBitrateCalculator(BitrateDuration, BitrateWindow, mode) t0 := time.Now() c.AddBytes(100, 100, t0) ts := t0.Add(100 * time.Millisecond) c.AddBytes(0, 0, ts) _, ok := c.Bitrate(ts) require.False(t, ok) }) } } // An idle drain followed by a backlog: BusyOnly counts the idle-drained bytes // over busy time only and reports a higher rate than ExcludeIdleDrain. func TestBitrateCalculatorMixedIdleThenBacklog(t *testing.T) { off := NewBitrateCalculator(BitrateDuration, BitrateWindow, BitrateModeBusyOnly) on := NewBitrateCalculator(BitrateDuration, BitrateWindow, BitrateModeExcludeIdleDrain) t0 := time.Now() feed := func(written, buffered int, ts time.Time) { off.AddBytes(written, buffered, ts) on.AddBytes(written, buffered, ts) } // 5000 drains during an idle gap (not backlogged), then a sustained backlog // of 800 every 100ms over a standing 5000. feed(5000, 5000, t0) feed(5000, 5000, t0.Add(100*time.Millisecond)) for i := 1; i <= 5; i++ { feed(800, 5800, t0.Add(time.Duration(100+i*100)*time.Millisecond)) } queryTs := t0.Add(700 * time.Millisecond) rOff, okOff := off.Bitrate(queryTs) rOn, okOn := on.Bitrate(queryTs) require.True(t, okOff) require.True(t, okOn) require.Greater(t, rOff, rOn) } // No observable buffer: throughput over elapsed time. 1000 B every 100ms = // 80000 bps. func TestBitrateCalculatorWallClock(t *testing.T) { c := NewBitrateCalculator(BitrateDuration, BitrateWindow, BitrateModeWallClock) t0 := time.Now() for i := 0; i < 20; i++ { c.AddBytes(1000, 0, t0.Add(time.Duration(i)*100*time.Millisecond)) } bitrate, ok := c.Bitrate(t0.Add(2 * time.Second)) require.True(t, ok) require.InDelta(t, 80000, bitrate, 8000) }