Files
redapricot/client/unacked_test.go
T
2026-08-15 17:31:35 +08:00

113 lines
3.5 KiB
Go

package client
import (
"bytes"
"testing"
)
// The retained region is what a reattach replays from, so an off-by-one here is
// not a dropped byte but a spliced stream: the peer resumes mid-packet and the
// session dies in a way no round-trip test would attribute to this code.
func TestUnackedTracksOffsets(t *testing.T) {
var u unackedBuf
u.append([]byte("hello"))
u.append([]byte("world"))
if got := u.length(); got != 10 {
t.Fatalf("length = %d, want 10", got)
}
if got := u.end(); got != 10 {
t.Fatalf("end = %d, want 10", got)
}
if got := u.from(0); !bytes.Equal(got, []byte("helloworld")) {
t.Fatalf("from(0) = %q", got)
}
// A reattach replays from wherever the peer got to, which lands anywhere —
// including the middle of a chunk boundary.
if got := u.from(3); !bytes.Equal(got, []byte("loworld")) {
t.Fatalf("from(3) = %q", got)
}
if got := u.from(10); len(got) != 0 {
t.Fatalf("from(end) = %q, want empty", got)
}
}
func TestUnackedAdvanceDropsCreditedBytes(t *testing.T) {
var u unackedBuf
u.append([]byte("abcdefghij"))
u.advance(4)
if got := u.length(); got != 6 {
t.Fatalf("length after advance = %d, want 6", got)
}
if got := u.end(); got != 10 {
t.Fatalf("end must not move when bytes are dropped: got %d, want 10", got)
}
if got := u.from(4); !bytes.Equal(got, []byte("efghij")) {
t.Fatalf("from(4) = %q", got)
}
// Below the retained region: the peer named an offset we can no longer
// satisfy, which must be reported rather than silently clamped — replaying
// the wrong range is worse than refusing to replay.
if got := u.from(3); got != nil {
t.Fatalf("from(3) below base = %q, want nil", got)
}
if got := u.from(11); got != nil {
t.Fatalf("from(11) past end = %q, want nil", got)
}
}
// Interleaving appends and advances is the steady-state pattern: credit arrives
// every half window while the sender keeps writing. The buffer must stay exact
// across the compaction that eventually triggers.
func TestUnackedSurvivesInterleavedAppendAndAdvance(t *testing.T) {
var u unackedBuf
var sent []byte
var acked int64
for i := 0; i < 200; i++ {
chunk := bytes.Repeat([]byte{byte(i)}, 97)
sent = append(sent, chunk...)
// emit's order: reclaim what has been credited so far, then retain the
// new chunk. The base therefore trails the credit that arrived since.
base := acked
u.advance(base)
u.append(chunk)
if got, want := u.end(), int64(len(sent)); got != want {
t.Fatalf("round %d: end = %d, want %d", i, got, want)
}
if got, want := u.length(), len(sent)-int(base); got != want {
t.Fatalf("round %d: length = %d, want %d", i, got, want)
}
if got, want := u.from(base), sent[base:]; !bytes.Equal(got, want) {
t.Fatalf("round %d: retained region diverges from what was sent", i)
}
// The peer can only ever credit bytes it has actually received.
if i%3 == 0 {
if acked += 61; acked > int64(len(sent)) {
acked = int64(len(sent))
}
}
}
}
// Compaction reuses the backing array, so a stream that runs for hours must not
// grow one: this is a full window per stream, on both sides.
func TestUnackedReclaimsBackingArray(t *testing.T) {
var u unackedBuf
chunk := bytes.Repeat([]byte{7}, 4096)
for i := 0; i < 500; i++ {
u.advance(u.end()) // fully credited every round
u.append(chunk)
}
if u.length() != len(chunk) {
t.Fatalf("length = %d, want %d", u.length(), len(chunk))
}
if cap(u.buf) > 8*len(chunk) {
t.Fatalf("backing array grew to %d bytes for a %d-byte window", cap(u.buf), len(chunk))
}
}