2026-08-04 15:46:12 -07:00
|
|
|
package main
|
|
|
|
|
|
2026-08-31 14:33:09 -07:00
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
2026-08-04 15:46:12 -07:00
|
|
|
|
2026-08-04 20:41:16 -07:00
|
|
|
// sent is the sender's frontier: sequence numbers at or above it were never put
|
|
|
|
|
// on the wire.
|
|
|
|
|
func newWindow(sent uint64) *lossWindow {
|
|
|
|
|
tx := &txStats{}
|
|
|
|
|
tx.sent.Store(sent)
|
|
|
|
|
w := newLossWindows([]*txStats{tx})
|
2026-08-04 15:46:12 -07:00
|
|
|
return &w[0]
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 14:33:09 -07:00
|
|
|
func TestLossContiguousLosesNothing(t *testing.T) {
|
|
|
|
|
w := newWindow(10000)
|
|
|
|
|
for seq := uint64(0); seq < 10000; seq++ {
|
|
|
|
|
w.observe(seq)
|
2026-08-04 15:46:12 -07:00
|
|
|
}
|
|
|
|
|
if got := w.lost.Load(); got != 0 {
|
|
|
|
|
t.Errorf("lost = %d, want 0", got)
|
|
|
|
|
}
|
|
|
|
|
if got := w.late.Load(); got != 0 {
|
|
|
|
|
t.Errorf("late = %d, want 0", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 14:33:09 -07:00
|
|
|
// An arrival is the judgment: the gap between it and the previous arrival is
|
|
|
|
|
// lost the moment it lands, not when some window slides.
|
|
|
|
|
func TestLossGapChargesOnArrival(t *testing.T) {
|
|
|
|
|
w := newWindow(10000)
|
|
|
|
|
for seq := uint64(0); seq < 100; seq++ {
|
|
|
|
|
w.observe(seq)
|
2026-08-04 15:46:12 -07:00
|
|
|
}
|
2026-08-31 14:33:09 -07:00
|
|
|
w.observe(105)
|
|
|
|
|
if got := w.lost.Load(); got != 5 {
|
|
|
|
|
t.Errorf("lost = %d, want 5 the moment the gap's far edge arrived", got)
|
2026-08-04 15:46:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 14:33:09 -07:00
|
|
|
// No tolerance of reordering: the newer frame writes the span off, and the
|
|
|
|
|
// older one arriving after it is late, not a repair.
|
|
|
|
|
func TestLossReorderChargesAndCountsLate(t *testing.T) {
|
|
|
|
|
w := newWindow(10000)
|
|
|
|
|
for seq := uint64(0); seq < 6; seq++ {
|
|
|
|
|
w.observe(seq)
|
|
|
|
|
}
|
|
|
|
|
w.observe(7)
|
|
|
|
|
if got := w.lost.Load(); got != 1 {
|
|
|
|
|
t.Errorf("lost = %d after the skip, want 1", got)
|
2026-08-04 15:46:12 -07:00
|
|
|
}
|
2026-08-31 14:33:09 -07:00
|
|
|
w.observe(6)
|
2026-08-04 15:46:12 -07:00
|
|
|
if got := w.late.Load(); got != 1 {
|
|
|
|
|
t.Errorf("late = %d, want 1", got)
|
|
|
|
|
}
|
2026-08-31 14:33:09 -07:00
|
|
|
if got := w.lost.Load(); got != 1 {
|
|
|
|
|
t.Errorf("lost = %d after the late arrival, want still 1", got)
|
2026-08-04 15:46:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
2026-08-04 20:41:16 -07:00
|
|
|
|
2026-08-31 14:33:09 -07:00
|
|
|
// A silent wire is charged by the judge within one deadline: everything the
|
|
|
|
|
// frontier covered a deadline ago and nothing has delivered since is lost.
|
|
|
|
|
func TestLossJudgeChargesSilentWire(t *testing.T) {
|
|
|
|
|
w := newWindow(1000)
|
|
|
|
|
t0 := time.Now()
|
|
|
|
|
w.judge(t0)
|
|
|
|
|
w.judge(t0.Add(lossDeadline / 2))
|
2026-08-04 20:41:16 -07:00
|
|
|
if got := w.lost.Load(); got != 0 {
|
2026-08-31 14:33:09 -07:00
|
|
|
t.Errorf("lost = %d before the deadline, want 0", got)
|
2026-08-04 20:41:16 -07:00
|
|
|
}
|
2026-08-31 14:33:09 -07:00
|
|
|
w.judge(t0.Add(lossDeadline))
|
|
|
|
|
if got := w.lost.Load(); got != 1000 {
|
|
|
|
|
t.Errorf("lost = %d after the deadline, want the whole silent frontier, 1000", got)
|
2026-08-04 20:41:16 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 14:33:09 -07:00
|
|
|
// Arrivals move next past the old frontier, so the judge has nothing to add.
|
|
|
|
|
func TestLossJudgeSkipsDelivered(t *testing.T) {
|
|
|
|
|
w := newWindow(1000)
|
|
|
|
|
t0 := time.Now()
|
|
|
|
|
w.judge(t0)
|
|
|
|
|
for seq := uint64(0); seq < 1000; seq++ {
|
|
|
|
|
w.observe(seq)
|
|
|
|
|
}
|
|
|
|
|
w.judge(t0.Add(lossDeadline))
|
2026-08-17 11:25:31 -07:00
|
|
|
if got := w.lost.Load(); got != 0 {
|
2026-08-31 14:33:09 -07:00
|
|
|
t.Errorf("lost = %d, want 0", got)
|
2026-08-17 11:25:31 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 14:33:09 -07:00
|
|
|
// A frame the judge already wrote off arriving afterwards is late, and the
|
|
|
|
|
// charge is not repaired.
|
|
|
|
|
func TestLossJudgedFrameArrivingIsLate(t *testing.T) {
|
|
|
|
|
w := newWindow(1000)
|
|
|
|
|
t0 := time.Now()
|
|
|
|
|
w.judge(t0)
|
|
|
|
|
w.judge(t0.Add(lossDeadline))
|
|
|
|
|
w.observe(500)
|
|
|
|
|
if got := w.late.Load(); got != 1 {
|
|
|
|
|
t.Errorf("late = %d, want 1", got)
|
2026-08-04 20:41:16 -07:00
|
|
|
}
|
2026-08-31 14:33:09 -07:00
|
|
|
if got := w.lost.Load(); got != 1000 {
|
|
|
|
|
t.Errorf("lost = %d, want unchanged 1000", got)
|
2026-08-04 20:41:16 -07:00
|
|
|
}
|
|
|
|
|
}
|