github.com/koko1123/flow-go-1@v0.29.6/engine/enqueue_test.go (about)

     1  package engine_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/rs/zerolog"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/koko1123/flow-go-1/engine"
    14  	"github.com/koko1123/flow-go-1/engine/common/fifoqueue"
    15  	"github.com/koko1123/flow-go-1/model/flow"
    16  	"github.com/koko1123/flow-go-1/utils/unittest"
    17  )
    18  
    19  // TestEngine tests the integration of MessageHandler and FifoQueue that buffer and deliver
    20  // matched messages to corresponding handlers
    21  type TestEngine struct {
    22  	unit           *engine.Unit
    23  	log            zerolog.Logger
    24  	ready          sync.WaitGroup
    25  	messageHandler *engine.MessageHandler
    26  	queueA         *engine.FifoMessageStore
    27  	queueB         *engine.FifoMessageStore
    28  
    29  	mu       sync.RWMutex
    30  	messages []interface{}
    31  }
    32  
    33  type messageA struct {
    34  	n int
    35  }
    36  
    37  type messageB struct {
    38  	n int
    39  }
    40  
    41  type messageC struct {
    42  	s string
    43  }
    44  
    45  func NewEngine(log zerolog.Logger, capacity int) (*TestEngine, error) {
    46  	fifoQueueA, err := fifoqueue.NewFifoQueue(capacity)
    47  	if err != nil {
    48  		return nil, fmt.Errorf("failed to create queue A: %w", err)
    49  	}
    50  
    51  	fifoQueueB, err := fifoqueue.NewFifoQueue(capacity)
    52  	if err != nil {
    53  		return nil, fmt.Errorf("failed to create queue B: %w", err)
    54  	}
    55  
    56  	queueA := &engine.FifoMessageStore{
    57  		FifoQueue: fifoQueueA,
    58  	}
    59  
    60  	queueB := &engine.FifoMessageStore{
    61  		FifoQueue: fifoQueueB,
    62  	}
    63  
    64  	// define message queueing behaviour
    65  	handler := engine.NewMessageHandler(
    66  		log,
    67  		engine.NewNotifier(),
    68  		engine.Pattern{
    69  			Match: func(msg *engine.Message) bool {
    70  				switch msg.Payload.(type) {
    71  				case *messageA:
    72  					return true
    73  				default:
    74  					return false
    75  				}
    76  			},
    77  			Store: queueA,
    78  		},
    79  		engine.Pattern{
    80  			Match: func(msg *engine.Message) bool {
    81  				switch msg.Payload.(type) {
    82  				case *messageB:
    83  					return true
    84  				default:
    85  					return false
    86  				}
    87  			},
    88  			Map: func(msg *engine.Message) (*engine.Message, bool) {
    89  				b := msg.Payload.(*messageB)
    90  				msg = &engine.Message{
    91  					OriginID: msg.OriginID,
    92  					Payload: &messageC{
    93  						s: fmt.Sprintf("c-%v", b.n),
    94  					},
    95  				}
    96  				return msg, true
    97  			},
    98  			Store: queueB,
    99  		},
   100  	)
   101  
   102  	eng := &TestEngine{
   103  		unit:           engine.NewUnit(),
   104  		log:            log,
   105  		messageHandler: handler,
   106  		queueA:         queueA,
   107  		queueB:         queueB,
   108  	}
   109  
   110  	return eng, nil
   111  }
   112  
   113  func (e *TestEngine) Process(originID flow.Identifier, event interface{}) error {
   114  	return e.messageHandler.Process(originID, event)
   115  }
   116  
   117  func (e *TestEngine) Ready() <-chan struct{} {
   118  	e.ready.Add(1)
   119  	e.unit.Launch(e.loop)
   120  	return e.unit.Ready(func() {
   121  		e.ready.Wait()
   122  	})
   123  }
   124  
   125  func (e *TestEngine) Done() <-chan struct{} {
   126  	return e.unit.Done()
   127  }
   128  
   129  func (e *TestEngine) loop() {
   130  	// let Ready() wait until the loop has started
   131  	// otherwise the message producer's doNotify will not be able to push messages
   132  	// to the e.notify channel
   133  	e.ready.Done()
   134  
   135  	for {
   136  		select {
   137  		case <-e.unit.Quit():
   138  			return
   139  		case <-e.messageHandler.GetNotifier():
   140  			e.log.Info().Msg("new message arrived")
   141  			err := e.processAvailableMessages()
   142  			if err != nil {
   143  				e.log.Fatal().Err(err).Msg("internal error processing message from the fifo queue")
   144  			}
   145  			e.log.Info().Msg("message processed")
   146  		}
   147  	}
   148  }
   149  
   150  func (e *TestEngine) processAvailableMessages() error {
   151  	for {
   152  		msg, ok := e.queueA.Get()
   153  		if ok {
   154  			err := e.OnProcessA(msg.OriginID, msg.Payload.(*messageA))
   155  			if err != nil {
   156  				return fmt.Errorf("could not handle block proposal: %w", err)
   157  			}
   158  			continue
   159  		}
   160  
   161  		msg, ok = e.queueB.Get()
   162  		if ok {
   163  			err := e.OnProcessC(msg.OriginID, msg.Payload.(*messageC))
   164  			if err != nil {
   165  				return fmt.Errorf("could not handle block vote: %w", err)
   166  			}
   167  			continue
   168  		}
   169  
   170  		// when there is no more messages in the queue, back to the loop to wait
   171  		// for the next incoming message to arrive.
   172  		return nil
   173  	}
   174  
   175  }
   176  
   177  func (e *TestEngine) OnProcessA(originID flow.Identifier, msg *messageA) error {
   178  	e.mu.Lock()
   179  	defer e.mu.Unlock()
   180  	e.messages = append(e.messages, msg)
   181  	return nil
   182  }
   183  
   184  func (e *TestEngine) OnProcessC(originID flow.Identifier, msg *messageC) error {
   185  	e.mu.Lock()
   186  	defer e.mu.Unlock()
   187  	e.messages = append(e.messages, msg)
   188  	return nil
   189  }
   190  
   191  func (e *TestEngine) MessageCount() int {
   192  	e.mu.RLock()
   193  	defer e.mu.RUnlock()
   194  	return len(e.messages)
   195  }
   196  
   197  func (e *TestEngine) AllCount() (int, int, int) {
   198  	e.mu.RLock()
   199  	defer e.mu.RUnlock()
   200  	return len(e.messages), e.queueA.Len(), e.queueB.Len()
   201  }
   202  
   203  func WithEngine(t *testing.T, f func(*TestEngine)) {
   204  	lg := unittest.Logger()
   205  	eng, err := NewEngine(lg, 150)
   206  	require.NoError(t, err)
   207  	<-eng.Ready()
   208  	f(eng)
   209  	<-eng.Done()
   210  }
   211  
   212  // if TestEngine receives messages of same type, the engine will handle them message
   213  func TestProcessMessageSameType(t *testing.T) {
   214  	id1 := unittest.IdentifierFixture()
   215  	id2 := unittest.IdentifierFixture()
   216  	m1 := &messageA{n: 1}
   217  	m2 := &messageA{n: 2}
   218  	m3 := &messageA{n: 3}
   219  	m4 := &messageA{n: 4}
   220  
   221  	WithEngine(t, func(eng *TestEngine) {
   222  		require.NoError(t, eng.Process(id1, m1))
   223  		require.NoError(t, eng.Process(id2, m2))
   224  		require.NoError(t, eng.Process(id1, m3))
   225  		require.NoError(t, eng.Process(id2, m4))
   226  
   227  		require.Eventuallyf(t, func() bool {
   228  			return eng.MessageCount() == 4
   229  		}, 2*time.Second, 10*time.Millisecond, "expect %v messages, but go %v messages",
   230  			4, eng.MessageCount())
   231  
   232  		eng.mu.Lock()
   233  		defer eng.mu.Unlock()
   234  		require.Equal(t, m1, eng.messages[0])
   235  		require.Equal(t, m2, eng.messages[1])
   236  		require.Equal(t, m3, eng.messages[2])
   237  		require.Equal(t, m4, eng.messages[3])
   238  	})
   239  }
   240  
   241  // if TestEngine receives messages of different types, the engine will handle them message
   242  func TestProcessMessageDifferentType(t *testing.T) {
   243  	id1 := unittest.IdentifierFixture()
   244  	id2 := unittest.IdentifierFixture()
   245  	m1 := &messageA{n: 1}
   246  	m2 := &messageA{n: 2}
   247  	m3 := &messageB{n: 3}
   248  	m4 := &messageB{n: 4}
   249  
   250  	WithEngine(t, func(eng *TestEngine) {
   251  		require.NoError(t, eng.Process(id1, m1))
   252  		require.NoError(t, eng.Process(id2, m2))
   253  		require.NoError(t, eng.Process(id1, m3))
   254  		require.NoError(t, eng.Process(id2, m4))
   255  
   256  		require.Eventuallyf(t, func() bool {
   257  			return eng.MessageCount() == 4
   258  		}, 2*time.Second, 10*time.Millisecond, "expect %v messages, but go %v messages",
   259  			4, eng.MessageCount())
   260  
   261  		eng.mu.Lock()
   262  		defer eng.mu.Unlock()
   263  		require.Equal(t, m1, eng.messages[0])
   264  		require.Equal(t, m2, eng.messages[1])
   265  		require.Equal(t, &messageC{s: "c-3"}, eng.messages[2])
   266  		require.Equal(t, &messageC{s: "c-4"}, eng.messages[3])
   267  	})
   268  }
   269  
   270  // if TestEngine receives messages in a period of time, they all will be handled
   271  func TestProcessMessageInterval(t *testing.T) {
   272  	id1 := unittest.IdentifierFixture()
   273  	id2 := unittest.IdentifierFixture()
   274  	m1 := &messageA{n: 1}
   275  	m2 := &messageA{n: 2}
   276  	m3 := &messageA{n: 3}
   277  	m4 := &messageA{n: 4}
   278  
   279  	WithEngine(t, func(eng *TestEngine) {
   280  		require.NoError(t, eng.Process(id1, m1))
   281  		time.Sleep(3 * time.Millisecond)
   282  		require.NoError(t, eng.Process(id2, m2))
   283  		time.Sleep(3 * time.Millisecond)
   284  		require.NoError(t, eng.Process(id1, m3))
   285  		time.Sleep(3 * time.Millisecond)
   286  		require.NoError(t, eng.Process(id2, m4))
   287  
   288  		require.Eventuallyf(t, func() bool {
   289  			return eng.MessageCount() == 4
   290  		}, 2*time.Second, 10*time.Millisecond, "expect %v messages, but go %v messages",
   291  			4, eng.MessageCount())
   292  		eng.mu.Lock()
   293  		defer eng.mu.Unlock()
   294  
   295  		require.Equal(t, m1, eng.messages[0])
   296  		require.Equal(t, m2, eng.messages[1])
   297  		require.Equal(t, m3, eng.messages[2])
   298  		require.Equal(t, m4, eng.messages[3])
   299  	})
   300  }
   301  
   302  // if TestEngine receives 100 messages for each type, the engine will handle them all
   303  func TestProcessMessageMultiAll(t *testing.T) {
   304  
   305  	WithEngine(t, func(eng *TestEngine) {
   306  		count := 100
   307  		for i := 0; i < count; i++ {
   308  			require.NoError(t, eng.Process(unittest.IdentifierFixture(), &messageA{n: i}))
   309  		}
   310  
   311  		require.Eventuallyf(t, func() bool {
   312  			return eng.MessageCount() == count
   313  		}, 2*time.Second, 10*time.Millisecond, "expect %v messages, but go %v messages",
   314  			count, eng.MessageCount())
   315  		require.Equal(t, count, eng.MessageCount())
   316  	})
   317  }
   318  
   319  // if TestEngine receives 100 messages for each type with interval, the engine will handle them all
   320  func TestProcessMessageMultiInterval(t *testing.T) {
   321  
   322  	WithEngine(t, func(eng *TestEngine) {
   323  		count := 100
   324  		for i := 0; i < count; i++ {
   325  			time.Sleep(1 * time.Millisecond)
   326  			require.NoError(t, eng.Process(unittest.IdentifierFixture(), &messageB{n: i}))
   327  		}
   328  
   329  		require.Eventuallyf(t, func() bool {
   330  			return eng.MessageCount() == count
   331  		}, 2*time.Second, 10*time.Millisecond, "expect %v messages, but go %v messages",
   332  			count, eng.MessageCount())
   333  	})
   334  }
   335  
   336  // if TestEngine receives 100 messages for each type concurrently, the engine will handle them all one after another
   337  func TestProcessMessageMultiConcurrent(t *testing.T) {
   338  
   339  	WithEngine(t, func(eng *TestEngine) {
   340  		count := 100
   341  		var sent sync.WaitGroup
   342  		for i := 0; i < count; i++ {
   343  			sent.Add(1)
   344  			go func(i int) {
   345  				require.NoError(t, eng.Process(unittest.IdentifierFixture(), &messageA{n: i}))
   346  				sent.Done()
   347  			}(i)
   348  		}
   349  		sent.Wait()
   350  
   351  		require.Eventuallyf(t, func() bool {
   352  			return eng.MessageCount() == count
   353  		}, 2*time.Second, 10*time.Millisecond, "expect %v messages, but go %v messages",
   354  			count, eng.MessageCount())
   355  	})
   356  }
   357  
   358  // TestUnknownMessageType verifies that the message handler returns an
   359  // IncompatibleInputTypeError when receiving a message of unknown type
   360  func TestUnknownMessageType(t *testing.T) {
   361  	WithEngine(t, func(eng *TestEngine) {
   362  		id := unittest.IdentifierFixture()
   363  		unknownType := struct{ n int }{n: 10}
   364  
   365  		err := eng.Process(id, unknownType)
   366  		require.Error(t, err)
   367  		require.True(t, errors.Is(err, engine.IncompatibleInputTypeError))
   368  	})
   369  }