mirror of
https://github.com/xtaci/kcp-go.git
synced 2026-10-19 16:14:38 +00:00
RingBuffer.Discard() used `end <= cap` to decide whether the discard range is contiguous. When head + n equals exactly len(elements), this incorrectly set head to len(elements) instead of wrapping it to 0. A subsequent Peek() or Push() would then access elements[len(elements)], triggering a runtime panic: "index out of range [64] with length 64". The crash path in production: KCP.Input -> parse_una -> snd_buf.Discard(count) -> shrink_buf -> snd_buf.Peek() -> panic Fix: change the boundary condition from `end <= cap` to `end < cap`, so the exact-boundary case falls through to the wrap-around branch which correctly computes head = end - cap = 0. Add TestRingBufferDiscardBoundary to cover this edge case.
302 lines
6.3 KiB
Go
302 lines
6.3 KiB
Go
package kcp
|
||
|
||
import (
|
||
"math/rand"
|
||
"testing"
|
||
)
|
||
|
||
func TestRingSize(t *testing.T) {
|
||
r := NewRingBuffer[int](1)
|
||
if r.Len() != 0 {
|
||
t.Errorf("Expected length 0, got %d", r.Len())
|
||
}
|
||
|
||
// re-zero
|
||
for i := range 64 {
|
||
r.Push(i)
|
||
r.Pop()
|
||
if r.Len() != 0 {
|
||
t.Errorf("Expected length 0 after pushing and popping, got %d", r.Len())
|
||
}
|
||
}
|
||
|
||
left := 1024 * 1024
|
||
for i := 0; i < left; i++ {
|
||
r.Push(i)
|
||
}
|
||
|
||
for {
|
||
use := rand.Int() % (left + 1)
|
||
left -= use
|
||
for range use {
|
||
if _, ok := r.Pop(); !ok {
|
||
t.Errorf("Expected to pop value, but got none")
|
||
}
|
||
}
|
||
if r.Len() != left {
|
||
t.Errorf("Expected length %d after popping, got %d", left, r.Len())
|
||
}
|
||
|
||
t.Log("use", use, "left", left, "head", r.head, "tail", r.tail, "len", r.Len(), "maxlen", r.MaxLen())
|
||
if left == 0 {
|
||
break
|
||
}
|
||
}
|
||
|
||
if r.Len() != 0 {
|
||
t.Errorf("Expected length 0 after pushing and popping, got %d", r.Len())
|
||
}
|
||
}
|
||
|
||
func TestRingSize2(t *testing.T) {
|
||
// emulate a condition where head = 32, tail=31, and Len() = 63
|
||
r := NewRingBuffer[int](64)
|
||
for i := range 63 {
|
||
r.Push(i)
|
||
}
|
||
for range 32 {
|
||
if _, ok := r.Pop(); !ok {
|
||
t.Errorf("Expected to pop value, but got none")
|
||
}
|
||
}
|
||
for i := range 32 {
|
||
r.Push(i + 63)
|
||
if r.Len() != i+(63-32+1) {
|
||
t.Errorf("Expected length %d after popping 31 elements, got %d", i+(63-32+1), r.Len())
|
||
}
|
||
}
|
||
|
||
// check head, tail and Len()
|
||
if r.head != 32 {
|
||
t.Errorf("Expected head to be 32, got %d", r.head)
|
||
}
|
||
if r.tail != 31 {
|
||
t.Errorf("Expected tail to be 31, got %d", r.tail)
|
||
}
|
||
if r.Len() != 63 {
|
||
t.Errorf("Expected length to be 63, got %d", r.Len())
|
||
}
|
||
|
||
t.Log("head", r.head, "tail", r.tail, "len", r.Len(), "maxlen", r.MaxLen())
|
||
|
||
// now we push one element to trigger grow
|
||
r.Push(95)
|
||
if r.MaxLen() != 127 {
|
||
t.Errorf("Expected capacity to be 128 after grow, got %d", r.MaxLen())
|
||
}
|
||
|
||
if r.Len() != 64 {
|
||
t.Errorf("Expected length to be 64 after pushing one element, got %d", r.Len())
|
||
}
|
||
|
||
if r.head != 0 {
|
||
t.Errorf("Expected head to be 0 after grow, got %d", r.head)
|
||
}
|
||
|
||
if r.tail != 64 {
|
||
t.Errorf("Expected tail to be 64 after grow, got %d", r.tail)
|
||
}
|
||
}
|
||
|
||
func TestRingBuffer(t *testing.T) {
|
||
r := NewRingBuffer[int](1)
|
||
if r.Len() != 0 {
|
||
t.Errorf("Expected length 0, got %d", r.Len())
|
||
}
|
||
|
||
for i := range 64 {
|
||
r.Push(i)
|
||
if r.Len() != i+1 {
|
||
t.Errorf("Expected length %d, got %d", i+1, r.Len())
|
||
}
|
||
}
|
||
|
||
for i := range 32 {
|
||
val, ok := r.Pop()
|
||
if !ok || val != i {
|
||
t.Errorf("Expected to pop %d, got %d (ok: %v)", i, val, ok)
|
||
}
|
||
}
|
||
|
||
if r.Len() != 32 {
|
||
t.Errorf("Expected length 32 after popping 32 elements, got %d", r.Len())
|
||
}
|
||
|
||
// Push more elements to test the ring's behavior
|
||
size := r.Len()
|
||
for i := range 32 {
|
||
r.Push(i)
|
||
if r.Len() != i+1+size {
|
||
t.Errorf("Expected length %d, got %d", i+1+size, r.Len())
|
||
}
|
||
}
|
||
|
||
if r.Len() != 64 {
|
||
t.Errorf("Expected length 64 after pushing 32 more elements, got %d", r.Len())
|
||
}
|
||
|
||
// ringbuffer should be [32 ... 63, 0 ... 31]
|
||
evicted := 0
|
||
round := 0
|
||
expectedHead := []int{62, 28}
|
||
for !r.IsEmpty() {
|
||
evicted += r.Discard(30)
|
||
if round < len(expectedHead) {
|
||
if v, ok := r.Peek(); !ok || *v != expectedHead[round] {
|
||
t.Errorf("Invalid discard state: unexpected %d", *v)
|
||
}
|
||
} else if _, ok := r.Peek(); ok {
|
||
t.Errorf("Unexpected non-nil head element")
|
||
}
|
||
if r.Len()+evicted != 64 {
|
||
t.Errorf("Unexpected ringbuffer length after discard op")
|
||
}
|
||
round++
|
||
}
|
||
}
|
||
|
||
func TestRingBufferGrow(t *testing.T) {
|
||
initialSize := 4
|
||
r := NewRingBuffer[int](initialSize)
|
||
|
||
expectedCapacities := []int{8, 16, 32, 64, 128}
|
||
|
||
// Push enough elements to trigger multiple grows
|
||
pushCount := 100
|
||
for i := range pushCount {
|
||
r.Push(i)
|
||
|
||
// Check if capacity is among expected
|
||
capacity := r.MaxLen()
|
||
valid := false
|
||
for _, ec := range expectedCapacities {
|
||
if capacity == ec-1 {
|
||
valid = true
|
||
break
|
||
}
|
||
}
|
||
if !valid && capacity <= RINGBUFFER_EXP {
|
||
t.Errorf("unexpected capacity during growth: %d", capacity)
|
||
}
|
||
}
|
||
|
||
// Push to 1024
|
||
for i := pushCount; i < 1024; i++ {
|
||
r.Push(i)
|
||
}
|
||
|
||
// Make sure the growth is 10% increase after 4096
|
||
r.Push(1)
|
||
if r.MaxLen() != 1126 {
|
||
t.Errorf("expected capacity to be 1126, got %d", r.MaxLen())
|
||
}
|
||
|
||
// Check values are preserved in correct order
|
||
for i := range pushCount {
|
||
v, ok := r.Pop()
|
||
if !ok {
|
||
t.Fatalf("expected to pop value at index %d", i)
|
||
return
|
||
}
|
||
if v != i {
|
||
t.Errorf("expected value %d, got %d", i, v)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestRingForEach(t *testing.T) {
|
||
r := NewRingBuffer[int](10)
|
||
for i := range 10 {
|
||
r.Push(i)
|
||
}
|
||
|
||
sum := 0
|
||
i := 0
|
||
r.ForEach(func(v *int) bool {
|
||
if *v != i {
|
||
t.Errorf("Expected index %d, got %d", i, *v)
|
||
}
|
||
i++
|
||
sum += *v
|
||
return true
|
||
})
|
||
|
||
if sum != 45 { // 0 + 1 + ... + 9 = 45
|
||
t.Errorf("Expected sum to be 45, got %d", sum)
|
||
}
|
||
|
||
count := 0
|
||
r.ForEach(func(v *int) bool {
|
||
count++
|
||
return true
|
||
})
|
||
|
||
if count != 10 {
|
||
t.Errorf("Expected count to be 10, got %d", count)
|
||
}
|
||
}
|
||
|
||
// TestRingBufferDiscardBoundary tests discarding elements that land exactly
|
||
// at the end of the backing array. This previously caused head to equal
|
||
// len(elements), leading to an index-out-of-range panic in Peek().
|
||
func TestRingBufferDiscardBoundary(t *testing.T) {
|
||
// Create a ring buffer with backing array length 64 (MaxLen = 63).
|
||
r := NewRingBuffer[int](64)
|
||
|
||
// Fill to capacity (63 elements), head=0, tail=63.
|
||
for i := range 63 {
|
||
r.Push(i)
|
||
}
|
||
|
||
// Pop 1 element so head=1, push 1 so tail wraps to 0. Still 63 elements.
|
||
r.Pop()
|
||
r.Push(63)
|
||
// Now head=1, tail=0, len=63.
|
||
|
||
// Discard exactly 63 elements. head + 63 = 64 = len(elements).
|
||
// This must wrap head to 0, not leave it at 64.
|
||
r.Discard(63)
|
||
|
||
if r.Len() != 0 {
|
||
t.Fatalf("expected empty ring, got Len()=%d", r.Len())
|
||
}
|
||
|
||
// Push a new element and Peek — this panicked before the fix.
|
||
r.Push(99)
|
||
if v, ok := r.Peek(); !ok || *v != 99 {
|
||
t.Fatalf("expected Peek()=99, got %v (ok=%v)", v, ok)
|
||
}
|
||
}
|
||
|
||
func TestRingForEachReverse(t *testing.T) {
|
||
r := NewRingBuffer[int](10)
|
||
for i := range 10 {
|
||
r.Push(i)
|
||
}
|
||
|
||
sum := 0
|
||
i := 9
|
||
r.ForEachReverse(func(v *int) bool {
|
||
if *v != i {
|
||
t.Errorf("Expected index %d, got %d", i, *v)
|
||
}
|
||
i--
|
||
sum += *v
|
||
return true
|
||
})
|
||
|
||
if sum != 45 { // 0 + 1 + ... + 9 = 45
|
||
t.Errorf("Expected sum to be 45, got %d", sum)
|
||
}
|
||
|
||
count := 0
|
||
r.ForEachReverse(func(v *int) bool {
|
||
count++
|
||
return true
|
||
})
|
||
|
||
if count != 10 {
|
||
t.Errorf("Expected count to be 10, got %d", count)
|
||
}
|
||
}
|