mirror of
https://github.com/AynaLivePlayer/AynaLivePlayer.git
synced 2025-12-06 10:22:50 +08:00
399 lines
11 KiB
Go
399 lines
11 KiB
Go
// generated by chatgpt
|
|
package eventbus
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestBasicLifecycle verifies the fundamental Start, Stop, and Wait operations.
|
|
func TestBasicLifecycle(t *testing.T) {
|
|
b := New(WithWorkerSize(2), WithQueueSize(10))
|
|
|
|
// Start should only work once.
|
|
err := b.Start()
|
|
require.NoError(t, err)
|
|
err = b.Start()
|
|
require.NoError(t, err) // Subsequent starts should be no-ops.
|
|
|
|
// Stop should work.
|
|
err = b.Stop()
|
|
require.NoError(t, err)
|
|
|
|
// Wait should not block after stop.
|
|
err = b.Wait()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// TestSubscribeAndPublish verifies the core functionality of publishing an event
|
|
// and having a subscriber receive it.
|
|
func TestSubscribeAndPublish(t *testing.T) {
|
|
b := New(WithWorkerSize(1), WithQueueSize(10))
|
|
err := b.Start()
|
|
require.NoError(t, err)
|
|
defer b.Stop()
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
|
|
receivedData := new(atomic.Value)
|
|
|
|
handler := func(event *Event) {
|
|
receivedData.Store(event.Data)
|
|
wg.Done()
|
|
}
|
|
|
|
err = b.Subscribe("", "test-event", "test-handler", handler)
|
|
require.NoError(t, err)
|
|
|
|
b.Publish("test-event", "hello world")
|
|
|
|
wg.Wait()
|
|
require.Equal(t, "hello world", receivedData.Load())
|
|
}
|
|
|
|
// TestUnsubscribe ensures that a handler stops receiving events after unsubscribing.
|
|
func TestUnsubscribe(t *testing.T) {
|
|
b := New(WithWorkerSize(2), WithQueueSize(10))
|
|
b.Start()
|
|
defer b.Stop()
|
|
|
|
var callCount int32
|
|
handler := func(event *Event) {
|
|
atomic.AddInt32(&callCount, 1)
|
|
}
|
|
|
|
err := b.Subscribe("", "event-A", "handler-1", handler)
|
|
require.NoError(t, err)
|
|
|
|
b.Publish("event-A", nil)
|
|
b.Wait()
|
|
require.Equal(t, int32(1), atomic.LoadInt32(&callCount))
|
|
|
|
// Unsubscribe
|
|
err = b.Unsubscribe("event-A", "handler-1")
|
|
require.NoError(t, err)
|
|
|
|
// Publish again
|
|
b.Publish("event-A", nil)
|
|
b.Wait() // Give it a moment to ensure it's not processed
|
|
time.Sleep(50 * time.Millisecond)
|
|
require.Equal(t, int32(1), atomic.LoadInt32(&callCount), "Handler should not be called after unsubscribing")
|
|
}
|
|
|
|
// TestSubscribeOnce verifies that a handler subscribed with SubscribeOnce
|
|
// is only called once and then automatically removed.
|
|
func TestSubscribeOnce(t *testing.T) {
|
|
b := New(WithWorkerSize(1), WithQueueSize(10))
|
|
b.Start()
|
|
defer b.Stop()
|
|
|
|
var callCount int32
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
|
|
handler := func(event *Event) {
|
|
atomic.AddInt32(&callCount, 1)
|
|
wg.Done()
|
|
}
|
|
|
|
err := b.SubscribeOnce("event-once", "handler-once", handler)
|
|
require.NoError(t, err)
|
|
|
|
// Publish twice
|
|
b.Publish("event-once", "data1")
|
|
wg.Wait() // Wait for the first event to be processed
|
|
|
|
b.Publish("event-once", "data2")
|
|
b.Wait()
|
|
time.Sleep(50 * time.Millisecond) // Ensure no more events are processed
|
|
|
|
require.Equal(t, int32(1), atomic.LoadInt32(&callCount))
|
|
}
|
|
|
|
// TestChannelSubscription validates that handlers correctly receive events based on channel matching.
|
|
func TestChannelSubscription(t *testing.T) {
|
|
b := New(WithWorkerSize(2), WithQueueSize(20))
|
|
b.Start()
|
|
defer b.Stop()
|
|
|
|
var receivedMu sync.Mutex
|
|
received := make(map[string]int)
|
|
|
|
// Handler for a specific channel
|
|
err := b.Subscribe("ch1", "event-X", "handler-ch1", func(event *Event) {
|
|
receivedMu.Lock()
|
|
received["handler-ch1"]++
|
|
receivedMu.Unlock()
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Handler for another channel
|
|
err = b.Subscribe("ch2", "event-X", "handler-ch2", func(event *Event) {
|
|
receivedMu.Lock()
|
|
received["handler-ch2"]++
|
|
receivedMu.Unlock()
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Handler for any channel (broadcast)
|
|
err = b.SubscribeAny("event-X", "handler-any", func(event *Event) {
|
|
receivedMu.Lock()
|
|
received["handler-any"]++
|
|
receivedMu.Unlock()
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// 1. Publish to ch1
|
|
b.PublishEvent(&Event{Id: "event-X", Channel: "ch1"})
|
|
b.Wait()
|
|
time.Sleep(50 * time.Millisecond)
|
|
receivedMu.Lock()
|
|
require.Equal(t, 1, received["handler-ch1"])
|
|
require.Equal(t, 0, received["handler-ch2"])
|
|
require.Equal(t, 1, received["handler-any"])
|
|
receivedMu.Unlock()
|
|
|
|
// 2. Publish to ch2
|
|
b.PublishEvent(&Event{Id: "event-X", Channel: "ch2"})
|
|
b.Wait()
|
|
time.Sleep(50 * time.Millisecond)
|
|
receivedMu.Lock()
|
|
require.Equal(t, 1, received["handler-ch1"])
|
|
require.Equal(t, 1, received["handler-ch2"])
|
|
require.Equal(t, 2, received["handler-any"])
|
|
receivedMu.Unlock()
|
|
|
|
// 3. Publish broadcast (empty channel)
|
|
b.PublishEvent(&Event{Id: "event-X", Channel: ""})
|
|
b.Wait()
|
|
time.Sleep(50 * time.Millisecond)
|
|
receivedMu.Lock()
|
|
// All handlers should receive broadcast events
|
|
require.Equal(t, 2, received["handler-ch1"])
|
|
require.Equal(t, 2, received["handler-ch2"])
|
|
require.Equal(t, 3, received["handler-any"])
|
|
receivedMu.Unlock()
|
|
}
|
|
|
|
// TestPublishBeforeStart ensures that events published before the bus starts are queued
|
|
// and processed after Start() is called.
|
|
func TestPublishBeforeStart(t *testing.T) {
|
|
b := New(WithWorkerSize(1), WithQueueSize(10))
|
|
|
|
var receivedCount int32
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
|
|
handler := func(event *Event) {
|
|
atomic.AddInt32(&receivedCount, 1)
|
|
wg.Done()
|
|
}
|
|
|
|
err := b.Subscribe("", "pending-event", "handler", handler)
|
|
require.NoError(t, err)
|
|
|
|
// Publish before start
|
|
b.Publish("pending-event", "data1")
|
|
b.Publish("pending-event", "data2")
|
|
|
|
// Handler should not have been called yet
|
|
require.Equal(t, int32(0), atomic.LoadInt32(&receivedCount))
|
|
|
|
// Now start the bus
|
|
b.Start()
|
|
defer b.Stop()
|
|
|
|
wg.Wait()
|
|
require.Equal(t, int32(2), atomic.LoadInt32(&receivedCount))
|
|
}
|
|
|
|
// TestCall validates the request-response pattern using the Call method.
|
|
func TestCall(t *testing.T) {
|
|
b := New(WithWorkerSize(2), WithQueueSize(10))
|
|
b.Start()
|
|
defer b.Stop()
|
|
|
|
// Responder handler
|
|
responder := func(event *Event) {
|
|
require.NotEmpty(t, event.EchoId)
|
|
// Respond with a different event ID, but the same EchoId
|
|
b.PublishEvent(&Event{
|
|
Id: "response-event",
|
|
EchoId: event.EchoId,
|
|
Data: fmt.Sprintf("response to %v", event.Data),
|
|
})
|
|
}
|
|
|
|
err := b.Subscribe("", "request-event", "responder-handler", responder)
|
|
require.NoError(t, err)
|
|
|
|
// Make the call
|
|
resp, err := b.Call("request-event", "my-data", "response-event")
|
|
|
|
// Verify response
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, "response-event", resp.Id)
|
|
require.Equal(t, "response to my-data", resp.Data)
|
|
}
|
|
|
|
// TestCall_StopDuringWait checks that Call returns an error if the bus is stopped while waiting.
|
|
func TestCall_StopDuringWait(t *testing.T) {
|
|
b := New(WithWorkerSize(1), WithQueueSize(10))
|
|
b.Start()
|
|
|
|
var callErr error
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
// This call will never get a response
|
|
_, callErr = b.Call("no-reply-event", nil, "no-reply-response")
|
|
}()
|
|
|
|
// Give the goroutine time to start waiting
|
|
time.Sleep(100 * time.Millisecond)
|
|
b.Stop() // Stop the bus
|
|
wg.Wait()
|
|
|
|
require.Error(t, callErr)
|
|
require.Contains(t, callErr.Error(), "bus stopped")
|
|
}
|
|
|
|
// TestPanicRecovery ensures that a panicking handler does not crash the worker.
|
|
func TestPanicRecovery(t *testing.T) {
|
|
b := New(WithWorkerSize(1), WithQueueSize(10))
|
|
b.Start()
|
|
defer b.Stop()
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(2) // One for the panic, one for the healthy one
|
|
|
|
panicHandler := func(event *Event) {
|
|
defer wg.Done()
|
|
if event.Data == "panic" {
|
|
panic("handler intended panic")
|
|
}
|
|
}
|
|
|
|
healthyHandler := func(event *Event) {
|
|
defer wg.Done()
|
|
// This should still run
|
|
}
|
|
|
|
err := b.Subscribe("", "panic-event", "panic-handler", panicHandler)
|
|
require.NoError(t, err)
|
|
err = b.Subscribe("", "panic-event", "healthy-handler", healthyHandler)
|
|
require.NoError(t, err)
|
|
|
|
// This should not crash the test
|
|
b.Publish("panic-event", "panic")
|
|
|
|
wg.Wait() // Will complete if both handlers finish (one by panicking, one normally)
|
|
require.True(t, true, "Test completed, indicating panic was recovered")
|
|
}
|
|
|
|
// TestConcurrency runs many operations in parallel to check for race conditions.
|
|
func TestConcurrency(t *testing.T) {
|
|
workerCount := 4
|
|
queueSize := 50
|
|
b := New(WithWorkerSize(workerCount), WithQueueSize(queueSize))
|
|
b.Start()
|
|
defer b.Stop()
|
|
|
|
numGoroutines := 50
|
|
eventsPerGoRoutine := 100
|
|
var totalEventsPublished int32
|
|
var totalEventsReceived int32
|
|
var wg sync.WaitGroup
|
|
|
|
// Subscriber goroutines
|
|
for i := 0; i < numGoroutines; i++ {
|
|
eventId := fmt.Sprintf("concurrent-event-%d", i%10) // 10 different events
|
|
handlerId := fmt.Sprintf("handler-%d", i)
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
handler := func(e *Event) {
|
|
atomic.AddInt32(&totalEventsReceived, 1)
|
|
}
|
|
err := b.Subscribe("", eventId, handlerId, handler)
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
err = b.Unsubscribe(eventId, handlerId)
|
|
require.NoError(t, err)
|
|
}()
|
|
}
|
|
|
|
// Setup a persistent handler to count received events
|
|
persistentHandler := func(e *Event) {
|
|
atomic.AddInt32(&totalEventsReceived, 1)
|
|
}
|
|
|
|
for i := 0; i < 20; i++ {
|
|
eventId := fmt.Sprintf("event-for-%d", i)
|
|
err := b.Subscribe("", eventId, "persistent-handler", persistentHandler)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Publisher goroutines
|
|
for i := 0; i < numGoroutines; i++ {
|
|
wg.Add(1)
|
|
go func(gId int) {
|
|
defer wg.Done()
|
|
for j := 0; j < eventsPerGoRoutine; j++ {
|
|
eventId := fmt.Sprintf("event-for-%d", (gId+j)%20)
|
|
b.Publish(eventId, gId)
|
|
atomic.AddInt32(&totalEventsPublished, 1)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
b.Wait() // Wait for all published events to be processed
|
|
|
|
fmt.Printf("Published: %d, Received: %d\n", totalEventsPublished, totalEventsReceived)
|
|
// We check that the number of received events matches published events for persistent handlers
|
|
require.Equal(t, atomic.LoadInt32(&totalEventsPublished), atomic.LoadInt32(&totalEventsReceived))
|
|
}
|
|
|
|
// TestInvalidArguments checks that API methods return errors on invalid input.
|
|
func TestInvalidArguments(t *testing.T) {
|
|
b := New(WithWorkerSize(1), WithQueueSize(1))
|
|
|
|
// Subscribe
|
|
err := b.Subscribe("", "", "name", func(e *Event) {})
|
|
require.Error(t, err, "Subscribe should error on empty eventId")
|
|
err = b.Subscribe("", "id", "", func(e *Event) {})
|
|
require.Error(t, err, "Subscribe should error on empty handlerName")
|
|
err = b.Subscribe("", "id", "name", nil)
|
|
require.Error(t, err, "Subscribe should error on nil handler func")
|
|
|
|
// SubscribeAny
|
|
err = b.SubscribeAny("", "name", func(e *Event) {})
|
|
require.Error(t, err, "SubscribeAny should error on empty eventId")
|
|
|
|
// SubscribeOnce
|
|
err = b.SubscribeOnce("", "name", func(e *Event) {})
|
|
require.Error(t, err, "SubscribeOnce should error on empty eventId")
|
|
|
|
// Unsubscribe
|
|
err = b.Unsubscribe("", "name")
|
|
require.Error(t, err, "Unsubscribe should error on empty eventId")
|
|
err = b.Unsubscribe("id", "")
|
|
require.Error(t, err, "Unsubscribe should error on empty handlerName")
|
|
|
|
// Call
|
|
_, err = b.Call("", nil, "subID")
|
|
require.Error(t, err, "Call should error on empty eventId")
|
|
}
|