diff --git a/common/queue/queue_test.go b/common/queue/queue_test.go new file mode 100644 index 00000000..64f023c9 --- /dev/null +++ b/common/queue/queue_test.go @@ -0,0 +1,215 @@ +package queue + +import ( + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +// TestQueuePut tests the Put method of Queue +func TestQueuePut(t *testing.T) { + // Initialize a new queue + q := New[int](10) + + // Test putting a single item + q.Put(1) + assert.Equal(t, int64(1), q.Len(), "Queue length should be 1 after putting one item") + + // Test putting multiple items + q.Put(2, 3, 4) + assert.Equal(t, int64(4), q.Len(), "Queue length should be 4 after putting three more items") + + // Test putting zero items (should not change queue) + q.Put() + assert.Equal(t, int64(4), q.Len(), "Queue length should remain unchanged when putting zero items") +} + +// TestQueuePop tests the Pop method of Queue +func TestQueuePop(t *testing.T) { + // Initialize a new queue with items + q := New[int](10) + q.Put(1, 2, 3) + + // Test popping items in FIFO order + item := q.Pop() + assert.Equal(t, 1, item, "First item popped should be 1") + assert.Equal(t, int64(2), q.Len(), "Queue length should be 2 after popping one item") + + item = q.Pop() + assert.Equal(t, 2, item, "Second item popped should be 2") + assert.Equal(t, int64(1), q.Len(), "Queue length should be 1 after popping two items") + + item = q.Pop() + assert.Equal(t, 3, item, "Third item popped should be 3") + assert.Equal(t, int64(0), q.Len(), "Queue length should be 0 after popping all items") +} + +// TestQueuePopEmpty tests the Pop method on an empty queue +func TestQueuePopEmpty(t *testing.T) { + // Initialize a new empty queue + q := New[int](0) + + // Test popping from an empty queue + item := q.Pop() + assert.Equal(t, 0, item, "Popping from an empty queue should return the zero value") + assert.Equal(t, int64(0), q.Len(), "Queue length should remain 0 after popping from an empty queue") +} + +// TestQueueLast tests the Last method of Queue +func TestQueueLast(t *testing.T) { + // Initialize a new queue with items + q := New[int](10) + q.Put(1, 2, 3) + + // Test getting the last item + item := q.Last() + assert.Equal(t, 3, item, "Last item should be 3") + assert.Equal(t, int64(3), q.Len(), "Queue length should remain unchanged after calling Last") + + // Test Last on an empty queue + emptyQ := New[int](0) + emptyItem := emptyQ.Last() + assert.Equal(t, 0, emptyItem, "Last on an empty queue should return the zero value") +} + +// TestQueueCopy tests the Copy method of Queue +func TestQueueCopy(t *testing.T) { + // Initialize a new queue with items + q := New[int](10) + q.Put(1, 2, 3) + + // Test copying the queue + copy := q.Copy() + assert.Equal(t, 3, len(copy), "Copy should have the same number of items as the original queue") + assert.Equal(t, 1, copy[0], "First item in copy should be 1") + assert.Equal(t, 2, copy[1], "Second item in copy should be 2") + assert.Equal(t, 3, copy[2], "Third item in copy should be 3") + + // Verify that modifying the copy doesn't affect the original queue + copy[0] = 99 + assert.Equal(t, 1, q.Pop(), "Original queue should not be affected by modifying the copy") +} + +// TestQueueLen tests the Len method of Queue +func TestQueueLen(t *testing.T) { + // Initialize a new empty queue + q := New[int](10) + assert.Equal(t, int64(0), q.Len(), "New queue should have length 0") + + // Add items and check length + q.Put(1, 2) + assert.Equal(t, int64(2), q.Len(), "Queue length should be 2 after putting two items") + + // Remove an item and check length + q.Pop() + assert.Equal(t, int64(1), q.Len(), "Queue length should be 1 after popping one item") +} + +// TestQueueNew tests the New constructor +func TestQueueNew(t *testing.T) { + // Test creating a new queue with different hints + q1 := New[int](0) + assert.NotNil(t, q1, "New queue should not be nil") + assert.Equal(t, int64(0), q1.Len(), "New queue should have length 0") + + q2 := New[int](10) + assert.NotNil(t, q2, "New queue should not be nil") + assert.Equal(t, int64(0), q2.Len(), "New queue should have length 0") + + // Test with a different type + q3 := New[string](5) + assert.NotNil(t, q3, "New queue should not be nil") + assert.Equal(t, int64(0), q3.Len(), "New queue should have length 0") +} + +// TestQueueConcurrency tests the concurrency safety of Queue +func TestQueueConcurrency(t *testing.T) { + // Initialize a new queue + q := New[int](100) + + // Number of goroutines and operations + goroutines := 10 + operations := 100 + + // Wait group to synchronize goroutines + wg := sync.WaitGroup{} + wg.Add(goroutines * 2) // For both producers and consumers + + // Start producer goroutines + for i := 0; i < goroutines; i++ { + go func(id int) { + defer wg.Done() + for j := 0; j < operations; j++ { + q.Put(id*operations + j) + // Small sleep to increase chance of race conditions + time.Sleep(time.Microsecond) + } + }(i) + } + + // Start consumer goroutines + consumed := make(chan int, goroutines*operations) + for i := 0; i < goroutines; i++ { + go func() { + defer wg.Done() + for j := 0; j < operations; j++ { + // Try to pop an item, but don't block if queue is empty + // Use a mutex to avoid race condition between Len() check and Pop() + q.lock.Lock() + if len(q.items) > 0 { + item := q.items[0] + q.items = q.items[1:] + q.lock.Unlock() + consumed <- item + } else { + q.lock.Unlock() + } + // Small sleep to increase chance of race conditions + time.Sleep(time.Microsecond) + } + }() + } + + // Wait for all goroutines to finish + wg.Wait() + // Close the consumed channel + close(consumed) + + // Count the number of consumed items + consumedCount := 0 + for range consumed { + consumedCount++ + } + + // Check that the queue is in a consistent state + totalItems := goroutines * operations + remaining := int(q.Len()) + assert.Equal(t, totalItems, consumedCount+remaining, "Total items should equal consumed items plus remaining items") +} + +// TestQueueWithDifferentTypes tests the Queue with different types +func TestQueueWithDifferentTypes(t *testing.T) { + // Test with string type + qString := New[string](5) + qString.Put("hello", "world") + assert.Equal(t, int64(2), qString.Len(), "Queue length should be 2") + assert.Equal(t, "hello", qString.Pop(), "First item should be 'hello'") + assert.Equal(t, "world", qString.Pop(), "Second item should be 'world'") + + // Test with struct type + type Person struct { + Name string + Age int + } + + qStruct := New[Person](5) + qStruct.Put(Person{Name: "Alice", Age: 30}, Person{Name: "Bob", Age: 25}) + assert.Equal(t, int64(2), qStruct.Len(), "Queue length should be 2") + + firstPerson := qStruct.Pop() + assert.Equal(t, "Alice", firstPerson.Name, "First person's name should be 'Alice'") + secondPerson := qStruct.Pop() + assert.Equal(t, "Bob", secondPerson.Name, "Second person's name should be 'Bob'") +}