Skip to content

Commit ce9f291

Browse files
authored
fix(circular): trim initial elements to capacity (#50)
* test(circular): add failing test for Reset exceeding capacity Constructing a Circular queue with 5 elements and capacity=3 should trim to 3 in every sense, including Reset. Current main stores all 5 as the reset target, so Reset restores size=5 on a 3-slot backing array, corrupting subsequent reads. Refs #40 * fix(circular): cap initialElems at capacity NewCircular trims the live elems slice to capacity but kept the full givenElems as the reset target. Reset would then restore a size larger than the backing array, making subsequent Get/Contains/Iterator read or iterate past the array's valid region. Trim initialElems to capacity so Reset produces a valid state. Fixes #40
1 parent d360b8c commit ce9f291

2 files changed

Lines changed: 56 additions & 17 deletions

File tree

circular.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,14 @@ func NewCircular[T comparable](
4848

4949
copy(elems, givenElems)
5050

51-
initialElems := make([]T, len(givenElems))
51+
// Cap initialElems at capacity so Reset cannot restore a size larger
52+
// than the backing array can hold.
53+
initialLen := len(givenElems)
54+
if initialLen > *options.capacity {
55+
initialLen = *options.capacity
56+
}
57+
58+
initialElems := make([]T, initialLen)
5259

5360
copy(initialElems, givenElems)
5461

circular_test.go

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -260,28 +260,60 @@ func testCircularIsEmpty(t *testing.T) {
260260
}
261261

262262
func testCircularReset(t *testing.T) {
263-
elems := []int{1, 2, 3, 4}
263+
t.Parallel()
264264

265-
circularQueue := queue.NewCircular(elems, 5)
265+
t.Run("Success", func(t *testing.T) {
266+
t.Parallel()
266267

267-
err := circularQueue.Offer(5)
268-
if err != nil {
269-
t.Fatalf("expected no error, got %v", err)
270-
}
268+
elems := []int{1, 2, 3, 4}
271269

272-
err = circularQueue.Offer(6)
273-
if err != nil {
274-
t.Fatalf("expected no error, got %v", err)
275-
}
270+
circularQueue := queue.NewCircular(elems, 5)
276271

277-
circularQueue.Reset()
272+
err := circularQueue.Offer(5)
273+
if err != nil {
274+
t.Fatalf("expected no error, got %v", err)
275+
}
278276

279-
queueElems := circularQueue.Clear()
280-
expectedElems := []int{1, 2, 3, 4}
277+
err = circularQueue.Offer(6)
278+
if err != nil {
279+
t.Fatalf("expected no error, got %v", err)
280+
}
281281

282-
if !reflect.DeepEqual(expectedElems, queueElems) {
283-
t.Fatalf("expected elements to be %v, got %v", expectedElems, queueElems)
284-
}
282+
circularQueue.Reset()
283+
284+
queueElems := circularQueue.Clear()
285+
expectedElems := []int{1, 2, 3, 4}
286+
287+
if !reflect.DeepEqual(expectedElems, queueElems) {
288+
t.Fatalf("expected elements to be %v, got %v", expectedElems, queueElems)
289+
}
290+
})
291+
292+
t.Run("InitialElemsExceedCapacity", func(t *testing.T) {
293+
t.Parallel()
294+
295+
// Capacity is 3 but 5 elements are provided. NewCircular trims the
296+
// live queue but keeps all 5 as the reset target, so Reset ends up
297+
// restoring size=5 on a backing array of length 3.
298+
circularQueue := queue.NewCircular([]int{1, 2, 3, 4, 5}, 3)
299+
300+
if size := circularQueue.Size(); size != 3 {
301+
t.Fatalf("expected size to be 3 after construction, got %d", size)
302+
}
303+
304+
circularQueue.Reset()
305+
306+
if size := circularQueue.Size(); size != 3 {
307+
t.Fatalf("expected size to be 3 after Reset, got %d", size)
308+
}
309+
310+
queueElems := circularQueue.Clear()
311+
expectedElems := []int{1, 2, 3}
312+
313+
if !reflect.DeepEqual(expectedElems, queueElems) {
314+
t.Fatalf("expected elements to be %v, got %v", expectedElems, queueElems)
315+
}
316+
})
285317
}
286318

287319
func testCircularIterator(t *testing.T) {

0 commit comments

Comments
 (0)