From 156fa3153c3146ef36141315d7083caf877e1bd7 Mon Sep 17 00:00:00 2001 From: ScuroNeko Date: Tue, 17 Mar 2026 14:37:35 +0300 Subject: [PATCH] fix: align slice/set equality and harden queue API --- queue.go | 46 ++++++++++++++++---------- queue_test.go | 92 +++++++++++++++++++++++++++++++++++++++++++++++---- set.go | 3 -- set_test.go | 12 +++++++ slice.go | 3 -- slice_test.go | 26 +++++++++++++++ 6 files changed, 153 insertions(+), 29 deletions(-) diff --git a/queue.go b/queue.go index dc4dbca..94d1719 100644 --- a/queue.go +++ b/queue.go @@ -6,6 +6,7 @@ import ( ) var QueueFullErr = errors.New("queue full") +var QueueEmptyErr = errors.New("queue empty") type Queue[T any] struct { size uint64 @@ -27,37 +28,48 @@ func (q *Queue[T]) IsFull() bool { return q.Len() == q.size } func (q *Queue[T]) Size() uint64 { return q.size } func (q *Queue[T]) Enqueue(el T) error { - if q.IsFull() { + q.mu.Lock() + defer q.mu.Unlock() + + if uint64(len(q.queue)) == q.size { return QueueFullErr } q.queue = q.queue.Push(el) return nil } -func (q *Queue[T]) Dequeue() T { - q.mu.RLock() - el := q.queue.Get(0) - q.mu.RUnlock() +func (q *Queue[T]) Dequeue() (T, error) { + q.mu.Lock() + defer q.mu.Unlock() - if q.Len() == 1 { - q.mu.Lock() - q.queue = make(Slice[T], 0) - q.mu.Unlock() - return el + var zero T + if len(q.queue) == 0 { + return zero, QueueEmptyErr } - q.mu.Lock() - q.queue = q.queue.Pop(0) - q.mu.Unlock() - return el + el := q.queue[0] + copy(q.queue, q.queue[1:]) + q.queue[len(q.queue)-1] = zero + q.queue = q.queue[:len(q.queue)-1] + + return el, nil } -func (q *Queue[T]) Peak() T { +func (q *Queue[T]) Peak() (T, error) { q.mu.RLock() defer q.mu.RUnlock() - return q.queue.Get(0) + + var zero T + if len(q.queue) == 0 { + return zero, QueueEmptyErr + } + + return q.queue[0], nil } func (q *Queue[T]) Raw() Slice[T] { - return q.queue + q.mu.RLock() + defer q.mu.RUnlock() + + return NewSliceFrom(q.queue) } diff --git a/queue_test.go b/queue_test.go index 00b68bc..c2c0fb7 100644 --- a/queue_test.go +++ b/queue_test.go @@ -11,14 +11,19 @@ func ExampleCreateQueue() { if err != nil { panic(err) } - i := q.Dequeue() // <- Here we got 1 + i, err := q.Dequeue() // <- Here we got 1 + if err != nil { + panic(err) + } log.Println(i) } func BenchmarkQueue_Enqueue(b *testing.B) { b.StartTimer() q := CreateQueue[int](uint64(b.N)) for i := 0; i < b.N; i++ { - q.Enqueue(i) + if err := q.Enqueue(i); err != nil { + b.Fatal(err) + } } b.StopTimer() b.ReportAllocs() @@ -26,11 +31,15 @@ func BenchmarkQueue_Enqueue(b *testing.B) { func BenchmarkQueue_Dequeue(b *testing.B) { q := CreateQueue[int](uint64(b.N)) for i := 0; i < b.N; i++ { - q.Enqueue(i) + if err := q.Enqueue(i); err != nil { + b.Fatal(err) + } } b.StartTimer() for i := 0; i < b.N; i++ { - q.Dequeue() + if _, err := q.Dequeue(); err != nil { + b.Fatal(err) + } } b.StopTimer() b.ReportAllocs() @@ -39,11 +48,82 @@ func BenchmarkQueue_EnqueueDequeue(b *testing.B) { b.StartTimer() q := CreateQueue[int](uint64(b.N)) for i := 0; i < b.N; i++ { - q.Enqueue(i) + if err := q.Enqueue(i); err != nil { + b.Fatal(err) + } } for i := 0; i < b.N; i++ { - q.Dequeue() + if _, err := q.Dequeue(); err != nil { + b.Fatal(err) + } } b.StopTimer() b.ReportAllocs() } + +func TestQueue_Empty(t *testing.T) { + q := CreateQueue[int](1) + + if _, err := q.Peak(); err != QueueEmptyErr { + t.Fatalf("Peak() error = %v, want %v", err, QueueEmptyErr) + } + if _, err := q.Dequeue(); err != QueueEmptyErr { + t.Fatalf("Dequeue() error = %v, want %v", err, QueueEmptyErr) + } +} + +func TestQueue_BasicOperations(t *testing.T) { + q := CreateQueue[int](2) + + if err := q.Enqueue(1); err != nil { + t.Fatal(err) + } + if err := q.Enqueue(2); err != nil { + t.Fatal(err) + } + if err := q.Enqueue(3); err != QueueFullErr { + t.Fatalf("Enqueue() error = %v, want %v", err, QueueFullErr) + } + + head, err := q.Peak() + if err != nil { + t.Fatal(err) + } + if head != 1 { + t.Fatalf("Peak() = %d, want 1", head) + } + + first, err := q.Dequeue() + if err != nil { + t.Fatal(err) + } + if first != 1 { + t.Fatalf("Dequeue() = %d, want 1", first) + } + + second, err := q.Dequeue() + if err != nil { + t.Fatal(err) + } + if second != 2 { + t.Fatalf("Dequeue() = %d, want 2", second) + } +} + +func TestQueue_RawReturnsCopy(t *testing.T) { + q := CreateQueue[int](2) + if err := q.Enqueue(1); err != nil { + t.Fatal(err) + } + + raw := q.Raw() + raw[0] = 99 + + head, err := q.Peak() + if err != nil { + t.Fatal(err) + } + if head != 1 { + t.Fatalf("Peak() = %d, want 1", head) + } +} diff --git a/set.go b/set.go index af1ce8e..1b68d58 100644 --- a/set.go +++ b/set.go @@ -62,9 +62,6 @@ func (s Set[T]) Equal(s2 Set[T]) bool { if s.Len() != s2.Len() { return false } - if s.Cap() != s2.Cap() { - return false - } for i := range s { if !reflect.DeepEqual(s[i], s2[i]) { return false diff --git a/set_test.go b/set_test.go index d96ff2f..3bd4666 100644 --- a/set_test.go +++ b/set_test.go @@ -17,3 +17,15 @@ func TestSet(t *testing.T) { t.Error("s1 and s2 not equal") } } + +func TestSet_EqualIgnoresCapacity(t *testing.T) { + s1 := make(Set[int], 2, 2) + s2 := make(Set[int], 2, 4) + + s1[0], s1[1] = 1, 2 + s2[0], s2[1] = 1, 2 + + if !s1.Equal(s2) { + t.Error("s1 and s2 not equal") + } +} diff --git a/slice.go b/slice.go index 1381450..f2867c2 100644 --- a/slice.go +++ b/slice.go @@ -56,9 +56,6 @@ func (s Slice[T]) Equal(s2 Slice[T]) bool { if s.Len() != s2.Len() { return false } - if s.Cap() != s2.Cap() { - return false - } for i := range s { if !reflect.DeepEqual(s[i], s2[i]) { return false diff --git a/slice_test.go b/slice_test.go index 3258d45..ce80972 100644 --- a/slice_test.go +++ b/slice_test.go @@ -56,6 +56,18 @@ func TestSlice_Equal(t *testing.T) { } } +func TestSlice_EqualIgnoresCapacity(t *testing.T) { + a1 := make(Slice[int], 2, 2) + a2 := make(Slice[int], 2, 4) + + a1[0], a1[1] = 1, 2 + a2[0], a2[1] = 1, 2 + + if !a1.Equal(a2) { + t.Errorf("a1 != a2") + } +} + func TestSlice_Pop(t *testing.T) { a1 := make(Slice[int], 10) a2 := make(Slice[int], 9) @@ -76,3 +88,17 @@ func TestSlice_Pop(t *testing.T) { t.Errorf("a1 != a2") } } + +func TestSlice_PopFrontEqual(t *testing.T) { + a1 := make(Slice[int], 3, 5) + a2 := make(Slice[int], 2, 2) + + a1[0], a1[1], a1[2] = 0, 1, 2 + a2[0], a2[1] = 1, 2 + + a1 = a1.Pop(0) + + if !a1.Equal(a2) { + t.Errorf("a1 != a2") + } +}