github.com/prysmaticlabs/prysm@v1.4.4/shared/queue/priority_queue_test.go (about)

     1  package queue
     2  
     3  import (
     4  	"container/heap"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    10  )
    11  
    12  // Ensure we satisfy the heap.Interface
    13  var _ heap.Interface = &queue{}
    14  
    15  // some tests rely on the ordering of items from this method
    16  func testCases() (tc []*Item) {
    17  	// create a slice of items with priority / times offest by these seconds
    18  	for i, m := range []time.Duration{
    19  		5,
    20  		183600,  // 51 hours
    21  		15,      // 15 seconds
    22  		45,      // 45 seconds
    23  		900,     // 15 minutes
    24  		300,     // 5 minutes
    25  		7200,    // 2 hours
    26  		183600,  // 51 hours
    27  		7201,    // 2 hours, 1 second
    28  		115200,  // 32 hours
    29  		1209600, // 2 weeks
    30  	} {
    31  		n := time.Now()
    32  		ft := n.Add(time.Second * m)
    33  		tc = append(tc, &Item{
    34  			Key:      fmt.Sprintf("item-%d", i),
    35  			Value:    1,
    36  			Priority: ft.Unix(),
    37  		})
    38  	}
    39  	return
    40  }
    41  
    42  func TestPriorityQueue_New(t *testing.T) {
    43  	pq := New()
    44  
    45  	if len(pq.data) != len(pq.dataMap) || len(pq.data) != 0 {
    46  		t.Fatalf("error in queue/map size, expected data and map to be initialized, got (%d) and (%d)", len(pq.data), len(pq.dataMap))
    47  	}
    48  
    49  	if pq.Len() != 0 {
    50  		t.Fatalf("expected new queue to have zero size, got (%d)", pq.Len())
    51  	}
    52  }
    53  
    54  func TestPriorityQueue_Push(t *testing.T) {
    55  	pq := New()
    56  
    57  	// don't allow nil pushing
    58  	if err := pq.Push(nil); err == nil {
    59  		t.Fatal("Expected error on pushing nil")
    60  	}
    61  
    62  	tc := testCases()
    63  	tcl := len(tc)
    64  	for _, i := range tc {
    65  		if err := pq.Push(i); err != nil {
    66  			t.Fatal(err)
    67  		}
    68  	}
    69  
    70  	if pq.Len() != tcl {
    71  		t.Fatalf("error adding items, expected (%d) items, got (%d)", tcl, pq.Len())
    72  	}
    73  
    74  	testValidateInternalData(t, pq, len(tc), false)
    75  
    76  	item, err := pq.Pop()
    77  	if err != nil {
    78  		t.Fatalf("error popping item: %s", err)
    79  	}
    80  	if tc[0].Priority != item.Priority {
    81  		t.Fatalf("expected tc[0] and popped item to match, got (%q) and (%q)", tc[0], item.Priority)
    82  	}
    83  	if tc[0].Key != item.Key {
    84  		t.Fatalf("expected tc[0] and popped item to match, got (%q) and (%q)", tc[0], item.Priority)
    85  	}
    86  
    87  	testValidateInternalData(t, pq, len(tc)-1, false)
    88  
    89  	// push item with no key
    90  	dErr := pq.Push(tc[1])
    91  	if dErr != ErrDuplicateItem {
    92  		t.Fatal(err)
    93  	}
    94  	// push item with no key
    95  	tc[2].Key = ""
    96  	kErr := pq.Push(tc[2])
    97  	if kErr != nil && kErr.Error() != "error adding item: Item Key is required" {
    98  		t.Fatal(kErr)
    99  	}
   100  
   101  	testValidateInternalData(t, pq, len(tc)-1, false)
   102  
   103  	// check nil,nil error for not found
   104  	i, err := pq.PopByKey("empty")
   105  	if err != nil && i != nil {
   106  		t.Fatalf("expected nil error for PopByKey of non-existing key, got: %s", err)
   107  	}
   108  }
   109  
   110  func TestPriorityQueue_Pop(t *testing.T) {
   111  	pq := New()
   112  
   113  	tc := testCases()
   114  	for _, i := range tc {
   115  		if err := pq.Push(i); err != nil {
   116  			t.Fatal(err)
   117  		}
   118  	}
   119  
   120  	topItem, err := pq.Pop()
   121  	if err != nil {
   122  		t.Fatalf("error calling pop: %s", err)
   123  	}
   124  	if tc[0].Priority != topItem.Priority {
   125  		t.Fatalf("expected tc[0] and popped item to match, got (%q) and (%q)", tc[0], topItem.Priority)
   126  	}
   127  	if tc[0].Key != topItem.Key {
   128  		t.Fatalf("expected tc[0] and popped item to match, got (%q) and (%q)", tc[0], topItem.Priority)
   129  	}
   130  
   131  	var items []*Item
   132  	items = append(items, topItem)
   133  	// pop the remaining items, compare size of input and output
   134  	i, err := pq.Pop()
   135  	require.NoError(t, err)
   136  	for ; i != nil; i, err = pq.Pop() {
   137  		require.NoError(t, err)
   138  		items = append(items, i)
   139  	}
   140  	require.Equal(t, len(tc), len(items))
   141  }
   142  
   143  func TestPriorityQueue_PopByKey(t *testing.T) {
   144  	pq := New()
   145  
   146  	tc := testCases()
   147  	for _, i := range tc {
   148  		if err := pq.Push(i); err != nil {
   149  			t.Fatal(err)
   150  		}
   151  	}
   152  
   153  	// grab the top priority item, to capture it's value for checking later
   154  	item, err := pq.Pop()
   155  	require.NoError(t, err)
   156  	oldPriority := item.Priority
   157  	oldKey := item.Key
   158  
   159  	// push the item back on, so it gets removed with PopByKey and we verify
   160  	// the top item has changed later
   161  	require.NoError(t, pq.Push(item))
   162  
   163  	popKeys := []int{2, 4, 7, 1, 0}
   164  	for _, i := range popKeys {
   165  		item, err := pq.PopByKey(fmt.Sprintf("item-%d", i))
   166  		if err != nil {
   167  			t.Fatalf("failed to pop item-%d, \n\terr: %s\n\titem: %#v", i, err, item)
   168  		}
   169  	}
   170  
   171  	testValidateInternalData(t, pq, len(tc)-len(popKeys), false)
   172  
   173  	// grab the top priority item again, to compare with the top item priority
   174  	// from above
   175  	item, err = pq.Pop()
   176  	require.NoError(t, err)
   177  	newPriority := item.Priority
   178  	newKey := item.Key
   179  
   180  	if oldPriority == newPriority || oldKey == newKey {
   181  		t.Fatalf("expected old/new key and priority to differ, got (%s/%s) and (%d/%d)", oldKey, newKey, oldPriority, newPriority)
   182  	}
   183  
   184  	testValidateInternalData(t, pq, len(tc)-len(popKeys)-1, true)
   185  }
   186  
   187  // testValidateInternalData checks the internal data structure of the PriorityQueue
   188  // and verifies that items are in-sync. Use drain only at the end of a test,
   189  // because it will mutate the input queue
   190  func testValidateInternalData(t *testing.T, pq *PriorityQueue, expectedSize int, drain bool) {
   191  	actualSize := pq.Len()
   192  	if actualSize != expectedSize {
   193  		t.Fatalf("expected new queue size to be (%d), got (%d)", expectedSize, actualSize)
   194  	}
   195  
   196  	if len(pq.data) != len(pq.dataMap) || len(pq.data) != expectedSize {
   197  		t.Fatalf("error in queue/map size, expected data and map to be (%d), got (%d) and (%d)", expectedSize, len(pq.data), len(pq.dataMap))
   198  	}
   199  
   200  	if drain && pq.Len() > 0 {
   201  		// pop all the items, verify lengths
   202  		i, err := pq.Pop()
   203  		require.NoError(t, err)
   204  		for ; i != nil; i, err = pq.Pop() {
   205  			require.NoError(t, err)
   206  			expectedSize--
   207  			if len(pq.data) != len(pq.dataMap) || len(pq.data) != expectedSize {
   208  				t.Fatalf("error in queue/map size, expected data and map to be (%d), got (%d) and (%d)", expectedSize, len(pq.data), len(pq.dataMap))
   209  			}
   210  		}
   211  	}
   212  }