- package events
-
- import (
- "context"
- "fmt"
- "math/rand"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/tendermint/tendermint/libs/log"
- )
-
- // TestAddListenerForEventFireOnce sets up an EventSwitch, subscribes a single
- // listener to an event, and sends a string "data".
- func TestAddListenerForEventFireOnce(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- logger := log.NewTestingLogger(t)
-
- evsw := NewEventSwitch(logger)
- require.NoError(t, evsw.Start(ctx))
- t.Cleanup(evsw.Wait)
-
- messages := make(chan EventData)
- require.NoError(t, evsw.AddListenerForEvent("listener", "event",
- func(ctx context.Context, data EventData) error {
- // test there's no deadlock if we remove the listener inside a callback
- evsw.RemoveListener("listener")
- select {
- case messages <- data:
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- go evsw.FireEvent(ctx, "event", "data")
- received := <-messages
- if received != "data" {
- t.Errorf("message received does not match: %v", received)
- }
- }
-
- // TestAddListenerForEventFireMany sets up an EventSwitch, subscribes a single
- // listener to an event, and sends a thousand integers.
- func TestAddListenerForEventFireMany(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- logger := log.NewTestingLogger(t)
-
- evsw := NewEventSwitch(logger)
- require.NoError(t, evsw.Start(ctx))
- t.Cleanup(evsw.Wait)
-
- doneSum := make(chan uint64)
- doneSending := make(chan uint64)
- numbers := make(chan uint64, 4)
- // subscribe one listener for one event
- require.NoError(t, evsw.AddListenerForEvent("listener", "event",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- // collect received events
- go sumReceivedNumbers(numbers, doneSum)
- // go fire events
- go fireEvents(ctx, evsw, "event", doneSending, uint64(1))
- checkSum := <-doneSending
- close(numbers)
- eventSum := <-doneSum
- if checkSum != eventSum {
- t.Errorf("not all messages sent were received.\n")
- }
- }
-
- // TestAddListenerForDifferentEvents sets up an EventSwitch, subscribes a single
- // listener to three different events and sends a thousand integers for each
- // of the three events.
- func TestAddListenerForDifferentEvents(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- logger := log.NewTestingLogger(t)
-
- evsw := NewEventSwitch(logger)
- require.NoError(t, evsw.Start(ctx))
- t.Cleanup(evsw.Wait)
-
- doneSum := make(chan uint64)
- doneSending1 := make(chan uint64)
- doneSending2 := make(chan uint64)
- doneSending3 := make(chan uint64)
- numbers := make(chan uint64, 4)
- // subscribe one listener to three events
- require.NoError(t, evsw.AddListenerForEvent("listener", "event1",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener", "event2",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener", "event3",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- // collect received events
- go sumReceivedNumbers(numbers, doneSum)
- // go fire events
- go fireEvents(ctx, evsw, "event1", doneSending1, uint64(1))
- go fireEvents(ctx, evsw, "event2", doneSending2, uint64(1))
- go fireEvents(ctx, evsw, "event3", doneSending3, uint64(1))
- var checkSum uint64
- checkSum += <-doneSending1
- checkSum += <-doneSending2
- checkSum += <-doneSending3
- close(numbers)
- eventSum := <-doneSum
- if checkSum != eventSum {
- t.Errorf("not all messages sent were received.\n")
- }
- }
-
- // TestAddDifferentListenerForDifferentEvents sets up an EventSwitch,
- // subscribes a first listener to three events, and subscribes a second
- // listener to two of those three events, and then sends a thousand integers
- // for each of the three events.
- func TestAddDifferentListenerForDifferentEvents(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- logger := log.NewTestingLogger(t)
- evsw := NewEventSwitch(logger)
- require.NoError(t, evsw.Start(ctx))
-
- t.Cleanup(evsw.Wait)
-
- doneSum1 := make(chan uint64)
- doneSum2 := make(chan uint64)
- doneSending1 := make(chan uint64)
- doneSending2 := make(chan uint64)
- doneSending3 := make(chan uint64)
- numbers1 := make(chan uint64, 4)
- numbers2 := make(chan uint64, 4)
- // subscribe two listener to three events
- require.NoError(t, evsw.AddListenerForEvent("listener1", "event1",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers1 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener1", "event2",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers1 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener1", "event3",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers1 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener2", "event2",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers2 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener2", "event3",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers2 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- // collect received events for listener1
- go sumReceivedNumbers(numbers1, doneSum1)
- // collect received events for listener2
- go sumReceivedNumbers(numbers2, doneSum2)
- // go fire events
- go fireEvents(ctx, evsw, "event1", doneSending1, uint64(1))
- go fireEvents(ctx, evsw, "event2", doneSending2, uint64(1001))
- go fireEvents(ctx, evsw, "event3", doneSending3, uint64(2001))
- checkSumEvent1 := <-doneSending1
- checkSumEvent2 := <-doneSending2
- checkSumEvent3 := <-doneSending3
- checkSum1 := checkSumEvent1 + checkSumEvent2 + checkSumEvent3
- checkSum2 := checkSumEvent2 + checkSumEvent3
- close(numbers1)
- close(numbers2)
- eventSum1 := <-doneSum1
- eventSum2 := <-doneSum2
- if checkSum1 != eventSum1 ||
- checkSum2 != eventSum2 {
- t.Errorf("not all messages sent were received for different listeners to different events.\n")
- }
- }
-
- func TestAddAndRemoveListenerConcurrency(t *testing.T) {
- var (
- stopInputEvent = false
- roundCount = 2000
- )
-
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- logger := log.NewTestingLogger(t)
-
- evsw := NewEventSwitch(logger)
- require.NoError(t, evsw.Start(ctx))
- t.Cleanup(evsw.Wait)
-
- done1 := make(chan struct{})
- done2 := make(chan struct{})
-
- // Must be executed concurrently to uncover the data race.
- // 1. RemoveListener
- go func() {
- defer close(done1)
- for i := 0; i < roundCount; i++ {
- evsw.RemoveListener("listener")
- }
- }()
-
- // 2. AddListenerForEvent
- go func() {
- defer close(done2)
- for i := 0; i < roundCount; i++ {
- index := i
- // we explicitly ignore errors here, since the listener will sometimes be removed
- // (that's what we're testing)
- _ = evsw.AddListenerForEvent("listener", fmt.Sprintf("event%d", index),
- func(ctx context.Context, data EventData) error {
- t.Errorf("should not run callback for %d.\n", index)
- stopInputEvent = true
- return nil
- })
- }
- }()
-
- <-done1
- <-done2
-
- evsw.RemoveListener("listener") // remove the last listener
-
- for i := 0; i < roundCount && !stopInputEvent; i++ {
- evsw.FireEvent(ctx, fmt.Sprintf("event%d", i), uint64(1001))
- }
- }
-
- // TestAddAndRemoveListener sets up an EventSwitch, subscribes a listener to
- // two events, fires a thousand integers for the first event, then unsubscribes
- // the listener and fires a thousand integers for the second event.
- func TestAddAndRemoveListener(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- logger := log.NewTestingLogger(t)
- evsw := NewEventSwitch(logger)
- require.NoError(t, evsw.Start(ctx))
- t.Cleanup(evsw.Wait)
-
- doneSum1 := make(chan uint64)
- doneSum2 := make(chan uint64)
- doneSending1 := make(chan uint64)
- doneSending2 := make(chan uint64)
- numbers1 := make(chan uint64, 4)
- numbers2 := make(chan uint64, 4)
- // subscribe two listener to three events
- require.NoError(t, evsw.AddListenerForEvent("listener", "event1",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers1 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener", "event2",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers2 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- // collect received events for event1
- go sumReceivedNumbers(numbers1, doneSum1)
- // collect received events for event2
- go sumReceivedNumbers(numbers2, doneSum2)
- // go fire events
- go fireEvents(ctx, evsw, "event1", doneSending1, uint64(1))
- checkSumEvent1 := <-doneSending1
- // after sending all event1, unsubscribe for all events
- evsw.RemoveListener("listener")
- go fireEvents(ctx, evsw, "event2", doneSending2, uint64(1001))
- checkSumEvent2 := <-doneSending2
- close(numbers1)
- close(numbers2)
- eventSum1 := <-doneSum1
- eventSum2 := <-doneSum2
- if checkSumEvent1 != eventSum1 ||
- // correct value asserted by preceding tests, suffices to be non-zero
- checkSumEvent2 == uint64(0) ||
- eventSum2 != uint64(0) {
- t.Errorf("not all messages sent were received or unsubscription did not register.\n")
- }
- }
-
- // TestRemoveListener does basic tests on adding and removing
- func TestRemoveListener(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- logger := log.NewTestingLogger(t)
-
- evsw := NewEventSwitch(logger)
- require.NoError(t, evsw.Start(ctx))
- t.Cleanup(evsw.Wait)
-
- count := 10
- sum1, sum2 := 0, 0
- // add some listeners and make sure they work
- require.NoError(t, evsw.AddListenerForEvent("listener", "event1",
- func(ctx context.Context, data EventData) error {
- sum1++
- return nil
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener", "event2",
- func(ctx context.Context, data EventData) error {
- sum2++
- return nil
- }))
-
- for i := 0; i < count; i++ {
- evsw.FireEvent(ctx, "event1", true)
- evsw.FireEvent(ctx, "event2", true)
- }
- assert.Equal(t, count, sum1)
- assert.Equal(t, count, sum2)
-
- // remove one by event and make sure it is gone
- evsw.RemoveListenerForEvent("event2", "listener")
- for i := 0; i < count; i++ {
- evsw.FireEvent(ctx, "event1", true)
- evsw.FireEvent(ctx, "event2", true)
- }
- assert.Equal(t, count*2, sum1)
- assert.Equal(t, count, sum2)
-
- // remove the listener entirely and make sure both gone
- evsw.RemoveListener("listener")
- for i := 0; i < count; i++ {
- evsw.FireEvent(ctx, "event1", true)
- evsw.FireEvent(ctx, "event2", true)
- }
- assert.Equal(t, count*2, sum1)
- assert.Equal(t, count, sum2)
- }
-
- // TestAddAndRemoveListenersAsync sets up an EventSwitch, subscribes two
- // listeners to three events, and fires a thousand integers for each event.
- // These two listeners serve as the baseline validation while other listeners
- // are randomly subscribed and unsubscribed.
- // More precisely it randomly subscribes new listeners (different from the first
- // two listeners) to one of these three events. At the same time it starts
- // randomly unsubscribing these additional listeners from all events they are
- // at that point subscribed to.
- // NOTE: it is important to run this test with race conditions tracking on,
- // `go test -race`, to examine for possible race conditions.
- func TestRemoveListenersAsync(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
- logger := log.NewTestingLogger(t)
-
- evsw := NewEventSwitch(logger)
- require.NoError(t, evsw.Start(ctx))
- t.Cleanup(evsw.Wait)
-
- doneSum1 := make(chan uint64)
- doneSum2 := make(chan uint64)
- doneSending1 := make(chan uint64)
- doneSending2 := make(chan uint64)
- doneSending3 := make(chan uint64)
- numbers1 := make(chan uint64, 4)
- numbers2 := make(chan uint64, 4)
- // subscribe two listener to three events
- require.NoError(t, evsw.AddListenerForEvent("listener1", "event1",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers1 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener1", "event2",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers1 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener1", "event3",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers1 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener2", "event1",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers2 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener2", "event2",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers2 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- require.NoError(t, evsw.AddListenerForEvent("listener2", "event3",
- func(ctx context.Context, data EventData) error {
- select {
- case numbers2 <- data.(uint64):
- return nil
- case <-ctx.Done():
- return ctx.Err()
- }
- }))
- // collect received events for event1
- go sumReceivedNumbers(numbers1, doneSum1)
- // collect received events for event2
- go sumReceivedNumbers(numbers2, doneSum2)
- addListenersStress := func() {
- r1 := rand.New(rand.NewSource(time.Now().Unix()))
- r1.Seed(time.Now().UnixNano())
- for k := uint16(0); k < 400; k++ {
- listenerNumber := r1.Intn(100) + 3
- eventNumber := r1.Intn(3) + 1
- go evsw.AddListenerForEvent(fmt.Sprintf("listener%v", listenerNumber), //nolint:errcheck // ignore for tests
- fmt.Sprintf("event%v", eventNumber),
- func(context.Context, EventData) error { return nil })
- }
- }
- removeListenersStress := func() {
- r2 := rand.New(rand.NewSource(time.Now().Unix()))
- r2.Seed(time.Now().UnixNano())
- for k := uint16(0); k < 80; k++ {
- listenerNumber := r2.Intn(100) + 3
- go evsw.RemoveListener(fmt.Sprintf("listener%v", listenerNumber))
- }
- }
- addListenersStress()
- // go fire events
- go fireEvents(ctx, evsw, "event1", doneSending1, uint64(1))
- removeListenersStress()
- go fireEvents(ctx, evsw, "event2", doneSending2, uint64(1001))
- go fireEvents(ctx, evsw, "event3", doneSending3, uint64(2001))
- checkSumEvent1 := <-doneSending1
- checkSumEvent2 := <-doneSending2
- checkSumEvent3 := <-doneSending3
- checkSum := checkSumEvent1 + checkSumEvent2 + checkSumEvent3
- close(numbers1)
- close(numbers2)
- eventSum1 := <-doneSum1
- eventSum2 := <-doneSum2
- if checkSum != eventSum1 ||
- checkSum != eventSum2 {
- t.Errorf("not all messages sent were received.\n")
- }
- }
-
- //------------------------------------------------------------------------------
- // Helper functions
-
- // sumReceivedNumbers takes two channels and adds all numbers received
- // until the receiving channel `numbers` is closed; it then sends the sum
- // on `doneSum` and closes that channel. Expected to be run in a go-routine.
- func sumReceivedNumbers(numbers, doneSum chan uint64) {
- var sum uint64
- for {
- j, more := <-numbers
- sum += j
- if !more {
- doneSum <- sum
- close(doneSum)
- return
- }
- }
- }
-
- // fireEvents takes an EventSwitch and fires a thousand integers under
- // a given `event` with the integers mootonically increasing from `offset`
- // to `offset` + 999. It additionally returns the addition of all integers
- // sent on `doneChan` for assertion that all events have been sent, and enabling
- // the test to assert all events have also been received.
- func fireEvents(ctx context.Context, evsw Fireable, event string, doneChan chan uint64, offset uint64) {
- defer close(doneChan)
-
- var sentSum uint64
- for i := offset; i <= offset+uint64(999); i++ {
- if ctx.Err() != nil {
- break
- }
-
- evsw.FireEvent(ctx, event, i)
- sentSum += i
- }
-
- select {
- case <-ctx.Done():
- case doneChan <- sentSum:
- }
- }
|