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 }