go.etcd.io/etcd@v3.3.27+incompatible/integration/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 integration
    16  
    17  import (
    18  	"fmt"
    19  	"math/rand"
    20  	"sync/atomic"
    21  	"testing"
    22  
    23  	"github.com/coreos/etcd/contrib/recipes"
    24  )
    25  
    26  const (
    27  	manyQueueClients    = 3
    28  	queueItemsPerClient = 2
    29  )
    30  
    31  // TestQueueOneReaderOneWriter confirms the queue is FIFO
    32  func TestQueueOneReaderOneWriter(t *testing.T) {
    33  	clus := NewClusterV3(t, &ClusterConfig{Size: 1})
    34  	defer clus.Terminate(t)
    35  
    36  	done := make(chan struct{})
    37  	go func() {
    38  		defer func() {
    39  			done <- struct{}{}
    40  		}()
    41  		etcdc := clus.RandClient()
    42  		q := recipe.NewQueue(etcdc, "testq")
    43  		for i := 0; i < 5; i++ {
    44  			if err := q.Enqueue(fmt.Sprintf("%d", i)); err != nil {
    45  				t.Fatalf("error enqueuing (%v)", err)
    46  			}
    47  		}
    48  	}()
    49  
    50  	etcdc := clus.RandClient()
    51  	q := recipe.NewQueue(etcdc, "testq")
    52  	for i := 0; i < 5; i++ {
    53  		s, err := q.Dequeue()
    54  		if err != nil {
    55  			t.Fatalf("error dequeueing (%v)", err)
    56  		}
    57  		if s != fmt.Sprintf("%d", i) {
    58  			t.Fatalf("expected dequeue value %v, got %v", s, i)
    59  		}
    60  	}
    61  	<-done
    62  }
    63  
    64  func TestQueueManyReaderOneWriter(t *testing.T) {
    65  	testQueueNReaderMWriter(t, manyQueueClients, 1)
    66  }
    67  
    68  func TestQueueOneReaderManyWriter(t *testing.T) {
    69  	testQueueNReaderMWriter(t, 1, manyQueueClients)
    70  }
    71  
    72  func TestQueueManyReaderManyWriter(t *testing.T) {
    73  	testQueueNReaderMWriter(t, manyQueueClients, manyQueueClients)
    74  }
    75  
    76  // BenchmarkQueue benchmarks Queues using many/many readers/writers
    77  func BenchmarkQueue(b *testing.B) {
    78  	// XXX switch tests to use TB interface
    79  	clus := NewClusterV3(nil, &ClusterConfig{Size: 3})
    80  	defer clus.Terminate(nil)
    81  	for i := 0; i < b.N; i++ {
    82  		testQueueNReaderMWriter(nil, manyQueueClients, manyQueueClients)
    83  	}
    84  }
    85  
    86  // TestPrQueueOneReaderOneWriter tests whether priority queues respect priorities.
    87  func TestPrQueueOneReaderOneWriter(t *testing.T) {
    88  	clus := NewClusterV3(t, &ClusterConfig{Size: 1})
    89  	defer clus.Terminate(t)
    90  
    91  	// write out five items with random priority
    92  	etcdc := clus.RandClient()
    93  	q := recipe.NewPriorityQueue(etcdc, "testprq")
    94  	for i := 0; i < 5; i++ {
    95  		// [0, 2] priority for priority collision to test seq keys
    96  		pr := uint16(rand.Intn(3))
    97  		if err := q.Enqueue(fmt.Sprintf("%d", pr), pr); err != nil {
    98  			t.Fatalf("error enqueuing (%v)", err)
    99  		}
   100  	}
   101  
   102  	// read back items; confirm priority order is respected
   103  	lastPr := -1
   104  	for i := 0; i < 5; i++ {
   105  		s, err := q.Dequeue()
   106  		if err != nil {
   107  			t.Fatalf("error dequeueing (%v)", err)
   108  		}
   109  		curPr := 0
   110  		if _, err := fmt.Sscanf(s, "%d", &curPr); err != nil {
   111  			t.Fatalf(`error parsing item "%s" (%v)`, s, err)
   112  		}
   113  		if lastPr > curPr {
   114  			t.Fatalf("expected priority %v > %v", curPr, lastPr)
   115  		}
   116  	}
   117  }
   118  
   119  func TestPrQueueManyReaderManyWriter(t *testing.T) {
   120  	clus := NewClusterV3(t, &ClusterConfig{Size: 3})
   121  	defer clus.Terminate(t)
   122  	rqs := newPriorityQueues(clus, manyQueueClients)
   123  	wqs := newPriorityQueues(clus, manyQueueClients)
   124  	testReadersWriters(t, rqs, wqs)
   125  }
   126  
   127  // BenchmarkQueue benchmarks Queues using n/n readers/writers
   128  func BenchmarkPrQueueOneReaderOneWriter(b *testing.B) {
   129  	// XXX switch tests to use TB interface
   130  	clus := NewClusterV3(nil, &ClusterConfig{Size: 3})
   131  	defer clus.Terminate(nil)
   132  	rqs := newPriorityQueues(clus, 1)
   133  	wqs := newPriorityQueues(clus, 1)
   134  	for i := 0; i < b.N; i++ {
   135  		testReadersWriters(nil, rqs, wqs)
   136  	}
   137  }
   138  
   139  func testQueueNReaderMWriter(t *testing.T, n int, m int) {
   140  	clus := NewClusterV3(t, &ClusterConfig{Size: 3})
   141  	defer clus.Terminate(t)
   142  	testReadersWriters(t, newQueues(clus, n), newQueues(clus, m))
   143  }
   144  
   145  func newQueues(clus *ClusterV3, n int) (qs []testQueue) {
   146  	for i := 0; i < n; i++ {
   147  		etcdc := clus.RandClient()
   148  		qs = append(qs, recipe.NewQueue(etcdc, "q"))
   149  	}
   150  	return qs
   151  }
   152  
   153  func newPriorityQueues(clus *ClusterV3, n int) (qs []testQueue) {
   154  	for i := 0; i < n; i++ {
   155  		etcdc := clus.RandClient()
   156  		q := &flatPriorityQueue{recipe.NewPriorityQueue(etcdc, "prq")}
   157  		qs = append(qs, q)
   158  	}
   159  	return qs
   160  }
   161  
   162  func testReadersWriters(t *testing.T, rqs []testQueue, wqs []testQueue) {
   163  	rerrc := make(chan error)
   164  	werrc := make(chan error)
   165  	manyWriters(wqs, queueItemsPerClient, werrc)
   166  	manyReaders(rqs, len(wqs)*queueItemsPerClient, rerrc)
   167  	for range wqs {
   168  		if err := <-werrc; err != nil {
   169  			t.Errorf("error writing (%v)", err)
   170  		}
   171  	}
   172  	for range rqs {
   173  		if err := <-rerrc; err != nil {
   174  			t.Errorf("error reading (%v)", err)
   175  		}
   176  	}
   177  }
   178  
   179  func manyReaders(qs []testQueue, totalReads int, errc chan<- error) {
   180  	var rxReads int32
   181  	for _, q := range qs {
   182  		go func(q testQueue) {
   183  			for {
   184  				total := atomic.AddInt32(&rxReads, 1)
   185  				if int(total) > totalReads {
   186  					break
   187  				}
   188  				if _, err := q.Dequeue(); err != nil {
   189  					errc <- err
   190  					return
   191  				}
   192  			}
   193  			errc <- nil
   194  		}(q)
   195  	}
   196  }
   197  
   198  func manyWriters(qs []testQueue, writesEach int, errc chan<- error) {
   199  	for _, q := range qs {
   200  		go func(q testQueue) {
   201  			for j := 0; j < writesEach; j++ {
   202  				if err := q.Enqueue("foo"); err != nil {
   203  					errc <- err
   204  					return
   205  				}
   206  			}
   207  			errc <- nil
   208  		}(q)
   209  	}
   210  }
   211  
   212  type testQueue interface {
   213  	Enqueue(val string) error
   214  	Dequeue() (string, error)
   215  }
   216  
   217  type flatPriorityQueue struct{ *recipe.PriorityQueue }
   218  
   219  func (q *flatPriorityQueue) Enqueue(val string) error {
   220  	// randomized to stress dequeuing logic; order isn't important
   221  	return q.PriorityQueue.Enqueue(val, uint16(rand.Intn(2)))
   222  }
   223  func (q *flatPriorityQueue) Dequeue() (string, error) {
   224  	return q.PriorityQueue.Dequeue()
   225  }