/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package queue import ( "context" "fmt" "sync" "sync/atomic" "testing" "time" ) type testMessage struct { ID int Data string } func TestQueue_SendAndReceive(t *testing.T) { q := New[*testMessage]("test", 10) defer q.Close() received := make(chan *testMessage, 1) q.RegisterHandler(func(ctx context.Context, msg *testMessage) error { received <- msg return nil }) msg := &testMessage{ID: 1, Data: "hello"} q.Send(context.Background(), msg) select { case r := <-received: if r.ID != msg.ID || r.Data != msg.Data { t.Errorf("received message mismatch: got %+v, want %+v", r, msg) } case <-time.After(time.Second): t.Fatal("timeout waiting for message") } } func TestQueue_MultipleMessages(t *testing.T) { q := New[*testMessage]("test", 10) defer q.Close() var count atomic.Int32 var wg sync.WaitGroup numMessages := 100 wg.Add(numMessages) q.RegisterHandler(func(ctx context.Context, msg *testMessage) error { count.Add(1) wg.Done() return nil }) for i := range numMessages { q.Send(context.Background(), &testMessage{ID: i}) } done := make(chan struct{}) go func() { wg.Wait() close(done) }() select { case <-done: if int(count.Load()) != numMessages { t.Errorf("expected %d messages, got %d", numMessages, count.Load()) } case <-time.After(5 * time.Second): t.Fatalf("timeout: only received %d of %d messages", count.Load(), numMessages) } } func TestQueue_NoHandlerDropsMessage(t *testing.T) { q := New[*testMessage]("test", 10) defer q.Close() // Send without handler - should not panic q.Send(context.Background(), &testMessage{ID: 1}) // Give time for the message to be processed (dropped) time.Sleep(100 * time.Millisecond) } func TestQueue_RegisterHandlerAfterSend(t *testing.T) { q := New[*testMessage]("test", 10) defer q.Close() received := make(chan *testMessage, 1) // Send first q.Send(context.Background(), &testMessage{ID: 1}) // Small delay then register handler time.Sleep(50 * time.Millisecond) q.RegisterHandler(func(ctx context.Context, msg *testMessage) error { received <- msg return nil }) // Send another message that should be received q.Send(context.Background(), &testMessage{ID: 2}) select { case r := <-received: if r.ID != 2 { // First message was dropped (no handler), second should be received t.Logf("received message ID: %d", r.ID) } case <-time.After(time.Second): t.Fatal("timeout waiting for message") } } func TestQueue_Close(t *testing.T) { q := New[*testMessage]("test", 10) var count atomic.Int32 q.RegisterHandler(func(ctx context.Context, msg *testMessage) error { count.Add(1) return nil }) // Send some messages for i := range 5 { q.Send(context.Background(), &testMessage{ID: i}) } // Close and wait q.Close() // All messages should have been processed if count.Load() != 5 { t.Errorf("expected 5 messages processed, got %d", count.Load()) } // Sending after close should not panic q.Send(context.Background(), &testMessage{ID: 99}) } func TestQueue_ConcurrentSend(t *testing.T) { q := New[*testMessage]("test", 100) defer q.Close() var count atomic.Int32 q.RegisterHandler(func(ctx context.Context, msg *testMessage) error { count.Add(1) return nil }) var wg sync.WaitGroup numGoroutines := 10 messagesPerGoroutine := 100 for i := range numGoroutines { wg.Add(1) go func(id int) { defer wg.Done() for j := range messagesPerGoroutine { q.Send(context.Background(), &testMessage{ID: id*1000 + j}) } }(i) } wg.Wait() // Wait for processing time.Sleep(500 * time.Millisecond) expected := int32(numGoroutines * messagesPerGoroutine) if count.Load() != expected { t.Errorf("expected %d messages, got %d", expected, count.Load()) } } func TestQueue_ConcurrentRegisterHandler(t *testing.T) { q := New[*testMessage]("test", 10) defer q.Close() // Concurrently register handlers - should not race var wg sync.WaitGroup for range 10 { wg.Go(func() { q.RegisterHandler(func(ctx context.Context, msg *testMessage) error { return nil }) }) } wg.Wait() } // TestQueue_SendCloseRace is a regression test for the race condition between // Send and Close. Without proper synchronization, concurrent Send and Close // calls could cause a "send on closed channel" panic. // Run with: go test -race -run TestQueue_SendCloseRace func TestQueue_SendCloseRace(t *testing.T) { for i := range 100 { t.Run(fmt.Sprintf("iteration_%d", i), func(t *testing.T) { // Use large buffer to avoid blocking on channel send while holding RLock q := New[*testMessage]("test-race", 1000) q.RegisterHandler(func(ctx context.Context, msg *testMessage) error { return nil }) var wg sync.WaitGroup // Use cancellable context so senders can exit when Close is called ctx, cancel := context.WithCancel(context.Background()) // Start multiple senders for j := range 10 { wg.Add(1) go func(id int) { defer wg.Done() for k := range 100 { q.Send(ctx, &testMessage{ID: id*1000 + k}) } }(j) } // Close while senders are still running go func() { time.Sleep(time.Microsecond * 10) cancel() // Cancel context to unblock any waiting senders q.Close() }() wg.Wait() }) } }