github.com/lfch/etcd-io/tests/v3@v3.0.0-20221004140520-eac99acd3e9d/integration/clientv3/experimental/recipes/v3_queue_test.go (about) 1 // Copyright 2016 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package recipes_test 16 17 import ( 18 "fmt" 19 "math/rand" 20 "sync/atomic" 21 "testing" 22 23 recipe "github.com/lfch/etcd-io/client/v3/experimental/recipes" 24 integration2 "github.com/lfch/etcd-io/tests/v3/framework/integration" 25 ) 26 27 const ( 28 manyQueueClients = 3 29 queueItemsPerClient = 2 30 ) 31 32 // TestQueueOneReaderOneWriter confirms the queue is FIFO 33 func TestQueueOneReaderOneWriter(t *testing.T) { 34 integration2.BeforeTest(t) 35 36 clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 1}) 37 defer clus.Terminate(t) 38 39 done := make(chan struct{}) 40 defer func() { 41 <-done 42 }() 43 go func() { 44 defer func() { 45 done <- struct{}{} 46 }() 47 etcdc := clus.RandClient() 48 q := recipe.NewQueue(etcdc, "testq") 49 for i := 0; i < 5; i++ { 50 if err := q.Enqueue(fmt.Sprintf("%d", i)); err != nil { 51 t.Errorf("error enqueuing (%v)", err) 52 } 53 } 54 }() 55 56 etcdc := clus.RandClient() 57 q := recipe.NewQueue(etcdc, "testq") 58 for i := 0; i < 5; i++ { 59 s, err := q.Dequeue() 60 if err != nil { 61 t.Fatalf("error dequeueing (%v)", err) 62 } 63 if s != fmt.Sprintf("%d", i) { 64 t.Fatalf("expected dequeue value %v, got %v", s, i) 65 } 66 } 67 } 68 69 func TestQueueManyReaderOneWriter(t *testing.T) { 70 testQueueNReaderMWriter(t, manyQueueClients, 1) 71 } 72 73 func TestQueueOneReaderManyWriter(t *testing.T) { 74 testQueueNReaderMWriter(t, 1, manyQueueClients) 75 } 76 77 func TestQueueManyReaderManyWriter(t *testing.T) { 78 testQueueNReaderMWriter(t, manyQueueClients, manyQueueClients) 79 } 80 81 // BenchmarkQueue benchmarks Queues using many/many readers/writers 82 func BenchmarkQueue(b *testing.B) { 83 integration2.BeforeTest(b) 84 85 // XXX switch tests to use TB interface 86 clus := integration2.NewCluster(nil, &integration2.ClusterConfig{Size: 3}) 87 defer clus.Terminate(nil) 88 for i := 0; i < b.N; i++ { 89 testQueueNReaderMWriter(nil, manyQueueClients, manyQueueClients) 90 } 91 } 92 93 // TestPrQueueOneReaderOneWriter tests whether priority queues respect priorities. 94 func TestPrQueueOneReaderOneWriter(t *testing.T) { 95 integration2.BeforeTest(t) 96 97 clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 1}) 98 defer clus.Terminate(t) 99 100 // write out five items with random priority 101 etcdc := clus.RandClient() 102 q := recipe.NewPriorityQueue(etcdc, "testprq") 103 for i := 0; i < 5; i++ { 104 // [0, 2] priority for priority collision to test seq keys 105 pr := uint16(rand.Intn(3)) 106 if err := q.Enqueue(fmt.Sprintf("%d", pr), pr); err != nil { 107 t.Fatalf("error enqueuing (%v)", err) 108 } 109 } 110 111 // read back items; confirm priority order is respected 112 lastPr := -1 113 for i := 0; i < 5; i++ { 114 s, err := q.Dequeue() 115 if err != nil { 116 t.Fatalf("error dequeueing (%v)", err) 117 } 118 curPr := 0 119 if _, err := fmt.Sscanf(s, "%d", &curPr); err != nil { 120 t.Fatalf(`error parsing item "%s" (%v)`, s, err) 121 } 122 if lastPr > curPr { 123 t.Fatalf("expected priority %v > %v", curPr, lastPr) 124 } 125 } 126 } 127 128 func TestPrQueueManyReaderManyWriter(t *testing.T) { 129 integration2.BeforeTest(t) 130 131 clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 3}) 132 defer clus.Terminate(t) 133 rqs := newPriorityQueues(clus, manyQueueClients) 134 wqs := newPriorityQueues(clus, manyQueueClients) 135 testReadersWriters(t, rqs, wqs) 136 } 137 138 // BenchmarkQueue benchmarks Queues using n/n readers/writers 139 func BenchmarkPrQueueOneReaderOneWriter(b *testing.B) { 140 integration2.BeforeTest(b) 141 142 // XXX switch tests to use TB interface 143 clus := integration2.NewCluster(nil, &integration2.ClusterConfig{Size: 3}) 144 defer clus.Terminate(nil) 145 rqs := newPriorityQueues(clus, 1) 146 wqs := newPriorityQueues(clus, 1) 147 for i := 0; i < b.N; i++ { 148 testReadersWriters(nil, rqs, wqs) 149 } 150 } 151 152 func testQueueNReaderMWriter(t *testing.T, n int, m int) { 153 integration2.BeforeTest(t) 154 clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 3}) 155 defer clus.Terminate(t) 156 testReadersWriters(t, newQueues(clus, n), newQueues(clus, m)) 157 } 158 159 func newQueues(clus *integration2.Cluster, n int) (qs []testQueue) { 160 for i := 0; i < n; i++ { 161 etcdc := clus.RandClient() 162 qs = append(qs, recipe.NewQueue(etcdc, "q")) 163 } 164 return qs 165 } 166 167 func newPriorityQueues(clus *integration2.Cluster, n int) (qs []testQueue) { 168 for i := 0; i < n; i++ { 169 etcdc := clus.RandClient() 170 q := &flatPriorityQueue{recipe.NewPriorityQueue(etcdc, "prq")} 171 qs = append(qs, q) 172 } 173 return qs 174 } 175 176 func testReadersWriters(t *testing.T, rqs []testQueue, wqs []testQueue) { 177 rerrc := make(chan error) 178 werrc := make(chan error) 179 manyWriters(wqs, queueItemsPerClient, werrc) 180 manyReaders(rqs, len(wqs)*queueItemsPerClient, rerrc) 181 for range wqs { 182 if err := <-werrc; err != nil { 183 t.Errorf("error writing (%v)", err) 184 } 185 } 186 for range rqs { 187 if err := <-rerrc; err != nil { 188 t.Errorf("error reading (%v)", err) 189 } 190 } 191 } 192 193 func manyReaders(qs []testQueue, totalReads int, errc chan<- error) { 194 var rxReads int32 195 for _, q := range qs { 196 go func(q testQueue) { 197 for { 198 total := atomic.AddInt32(&rxReads, 1) 199 if int(total) > totalReads { 200 break 201 } 202 if _, err := q.Dequeue(); err != nil { 203 errc <- err 204 return 205 } 206 } 207 errc <- nil 208 }(q) 209 } 210 } 211 212 func manyWriters(qs []testQueue, writesEach int, errc chan<- error) { 213 for _, q := range qs { 214 go func(q testQueue) { 215 for j := 0; j < writesEach; j++ { 216 if err := q.Enqueue("foo"); err != nil { 217 errc <- err 218 return 219 } 220 } 221 errc <- nil 222 }(q) 223 } 224 } 225 226 type testQueue interface { 227 Enqueue(val string) error 228 Dequeue() (string, error) 229 } 230 231 type flatPriorityQueue struct{ *recipe.PriorityQueue } 232 233 func (q *flatPriorityQueue) Enqueue(val string) error { 234 // randomized to stress dequeuing logic; order isn't important 235 return q.PriorityQueue.Enqueue(val, uint16(rand.Intn(2))) 236 } 237 func (q *flatPriorityQueue) Dequeue() (string, error) { 238 return q.PriorityQueue.Dequeue() 239 }