github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/queue/messageQueue_test.go (about)

     1  package queue_test
     2  
     3  import (
     4  	"context"
     5  	"math/rand"
     6  	"strconv"
     7  	"sync"
     8  	"sync/atomic"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  
    14  	"github.com/onflow/flow-go/module/metrics"
    15  	"github.com/onflow/flow-go/network/queue"
    16  )
    17  
    18  // TestRetrievalByPriority tests that message can be retrieved in priority order
    19  func TestRetrievalByPriority(t *testing.T) {
    20  	// create a map of messages -> priority with messages assigned random priorities
    21  	messages := createMessages(1000, randomPriority)
    22  	testQueue(t, messages)
    23  }
    24  
    25  // TestRetrievalByInsertionOrder tests that messages with the same priority can be retrieved in insertion order
    26  func TestRetrievalByInsertionOrder(t *testing.T) {
    27  
    28  	// create a map of messages -> priority with messages assigned fixed priorities
    29  	messages := createMessages(1000, fixedPriority)
    30  	testQueue(t, messages)
    31  }
    32  
    33  // TestConcurrentQueueAccess tests that the queue can be safely accessed concurrently
    34  func TestConcurrentQueueAccess(t *testing.T) {
    35  	writerCnt := 5
    36  	readerCnt := 5
    37  	messageCnt := 1000
    38  
    39  	messages := createMessages(messageCnt, randomPriority)
    40  
    41  	var priorityFunc queue.MessagePriorityFunc = func(message interface{}) (queue.Priority, error) {
    42  		return messages[message.(string)], nil
    43  	}
    44  
    45  	msgChan := make(chan string, len(messages))
    46  	for k := range messages {
    47  		msgChan <- k
    48  	}
    49  	close(msgChan)
    50  
    51  	ctx, cancel := context.WithCancel(context.Background())
    52  	mq := queue.NewMessageQueue(ctx, priorityFunc, metrics.NewNoopCollector())
    53  
    54  	writeWg := sync.WaitGroup{}
    55  	write := func() {
    56  		defer writeWg.Done()
    57  		for msg := range msgChan {
    58  			err := mq.Insert(msg)
    59  			assert.NoError(t, err)
    60  		}
    61  	}
    62  	var readMsgCnt int64
    63  	read := func() {
    64  		for {
    65  			elem := mq.Remove()
    66  			if elem == nil {
    67  				return
    68  			}
    69  			atomic.AddInt64(&readMsgCnt, 1)
    70  		}
    71  	}
    72  
    73  	// kick off writers
    74  	for i := 0; i < writerCnt; i++ {
    75  		writeWg.Add(1)
    76  		go write()
    77  	}
    78  
    79  	// kick off readers
    80  	for i := 0; i < readerCnt; i++ {
    81  		go read()
    82  	}
    83  
    84  	writeWg.Wait()
    85  
    86  	assert.Eventually(t, func() bool {
    87  		actualCnt := atomic.LoadInt64(&readMsgCnt)
    88  		return int64(messageCnt) == actualCnt
    89  	}, 5*time.Second, 5*time.Millisecond)
    90  
    91  	cancel()
    92  
    93  	assert.Equal(t, 0, mq.Len())
    94  }
    95  
    96  // TestQueueShutdown tests that Remove unblocks when the context is shutdown
    97  func TestQueueShutdown(t *testing.T) {
    98  	ctx, cancel := context.WithCancel(context.Background())
    99  	mq := queue.NewMessageQueue(ctx, fixedPriority, metrics.NewNoopCollector())
   100  	ch := make(chan struct{})
   101  
   102  	go func() {
   103  		mq.Remove()
   104  		close(ch)
   105  	}()
   106  
   107  	cancel()
   108  	assert.Eventually(t, func() bool {
   109  		select {
   110  		case <-ch:
   111  			return true
   112  		default:
   113  			return false
   114  		}
   115  	}, time.Second, time.Millisecond)
   116  }
   117  
   118  func testQueue(t *testing.T, messages map[string]queue.Priority) {
   119  
   120  	// create the priority function
   121  	var priorityFunc queue.MessagePriorityFunc = func(message interface{}) (queue.Priority, error) {
   122  		return messages[message.(string)], nil
   123  	}
   124  
   125  	// create queues for each priority to check expectations later
   126  	queues := make(map[queue.Priority][]string)
   127  	for p := queue.LowPriority; p <= queue.HighPriority; p++ {
   128  		queues[queue.Priority(p)] = make([]string, 0)
   129  	}
   130  
   131  	ctx, cancel := context.WithCancel(context.Background())
   132  	defer cancel()
   133  	// create the queue
   134  	mq := queue.NewMessageQueue(ctx, priorityFunc, metrics.NewNoopCollector())
   135  
   136  	// insert all elements in the queue
   137  	for msg, p := range messages {
   138  
   139  		err := mq.Insert(msg)
   140  		assert.NoError(t, err)
   141  
   142  		// remember insertion order to check later
   143  		queues[p] = append(queues[p], msg)
   144  	}
   145  
   146  	// create a slice of the expected messages in the order in which they are expected
   147  	var expectedMessages []string
   148  	for p := queue.HighPriority; p >= queue.LowPriority; p-- {
   149  		expectedMessages = append(expectedMessages, queues[queue.Priority(p)]...)
   150  	}
   151  
   152  	// check queue length
   153  	assert.Equal(t, len(expectedMessages), mq.Len())
   154  
   155  	// check that elements are retrieved in order
   156  	for i := 0; i < len(expectedMessages); i++ {
   157  
   158  		item := mq.Remove()
   159  
   160  		assert.Equal(t, expectedMessages[i], item.(string))
   161  	}
   162  
   163  	assert.Equal(t, 0, mq.Len())
   164  }
   165  
   166  func BenchmarkPush(b *testing.B) {
   167  	b.StopTimer()
   168  	ctx, cancel := context.WithCancel(context.Background())
   169  	defer cancel()
   170  	var mq = queue.NewMessageQueue(ctx, randomPriority, metrics.NewNoopCollector())
   171  	for i := 0; i < b.N; i++ {
   172  		err := mq.Insert("test")
   173  		if err != nil {
   174  			b.Error(err)
   175  		}
   176  	}
   177  	b.StartTimer()
   178  	for i := 0; i < b.N; i++ {
   179  		err := mq.Insert("test")
   180  		if err != nil {
   181  			b.Error(err)
   182  		}
   183  	}
   184  }
   185  
   186  func BenchmarkPop(b *testing.B) {
   187  	b.StopTimer()
   188  	ctx, cancel := context.WithCancel(context.Background())
   189  	defer cancel()
   190  	var mq = queue.NewMessageQueue(ctx, randomPriority, metrics.NewNoopCollector())
   191  	for i := 0; i < b.N; i++ {
   192  		err := mq.Insert("test")
   193  		if err != nil {
   194  			b.Error(err)
   195  		}
   196  	}
   197  	b.StartTimer()
   198  	for i := 0; i < b.N; i++ {
   199  		mq.Remove()
   200  	}
   201  }
   202  
   203  func createMessages(messageCnt int, priorityFunc queue.MessagePriorityFunc) map[string]queue.Priority {
   204  	msgPrefix := "message"
   205  	// create a map of messages -> priority
   206  	messages := make(map[string]queue.Priority, messageCnt)
   207  
   208  	for i := 0; i < messageCnt; i++ {
   209  		// choose a random priority
   210  		p, _ := priorityFunc(nil)
   211  		// create a message
   212  		msg := msgPrefix + strconv.Itoa(i)
   213  		messages[msg] = p
   214  	}
   215  
   216  	return messages
   217  }
   218  
   219  func randomPriority(_ interface{}) (queue.Priority, error) {
   220  
   221  	p := rand.Intn(int(queue.HighPriority-queue.LowPriority+1)) + int(queue.LowPriority)
   222  	return queue.Priority(p), nil
   223  }
   224  
   225  func fixedPriority(_ interface{}) (queue.Priority, error) {
   226  	return queue.MediumPriority, nil
   227  }