github.com/decred/dcrlnd@v0.7.6/queue/gc_queue_test.go (about) 1 package queue_test 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/decred/dcrlnd/queue" 8 ) 9 10 // testItem is an item type we'll be using to test the GCQueue. 11 type testItem uint32 12 13 // TestGCQueueGCCycle asserts that items that are kept in the GCQueue past their 14 // expiration will be released by a subsequent gc cycle. 15 func TestGCQueueGCCycle(t *testing.T) { 16 t.Parallel() 17 18 const ( 19 gcInterval = time.Second 20 expiryInterval = 250 * time.Millisecond 21 numItems = 6 22 ) 23 24 newItem := func() interface{} { return new(testItem) } 25 26 bp := queue.NewGCQueue(newItem, 100, gcInterval, expiryInterval) 27 28 // Take numItems items from the queue, and immediately return them. 29 // Returning the items will trigger the gc ticker to start. 30 itemSet1 := takeN(t, bp, numItems) 31 returnAll(bp, itemSet1) 32 33 // Allow enough time for all expired items to be released by the queue. 34 <-time.After(gcInterval + expiryInterval) 35 36 // Take another set of numItems items from the queue. 37 itemSet2 := takeN(t, bp, numItems) 38 39 // Since the gc ticker should have elapsed, we expect the intersection 40 // of sets 1 and 2 to be empty. 41 for item := range itemSet2 { 42 if _, ok := itemSet1[item]; ok { 43 t.Fatalf("items taken should not have been reused") 44 } 45 } 46 } 47 48 // TestGCQueuePartialGCCycle asserts that the GCQueue will only garbage collect 49 // the items in its queue that have fully expired. We test this by adding items 50 // into the queue such that the garbage collection will occur before the items 51 // expire. Taking items after the gc cycle should return the items that were not 52 // released by the gc cycle. 53 func TestGCQueuePartialGCCycle(t *testing.T) { 54 t.Parallel() 55 56 const ( 57 gcInterval = time.Second 58 expiryInterval = 250 * time.Millisecond 59 numItems = 6 60 ) 61 62 newItem := func() interface{} { return new(testItem) } 63 64 bp := queue.NewGCQueue(newItem, 100, gcInterval, expiryInterval) 65 66 // Take numItems items from the gc queue. 67 itemSet1 := takeN(t, bp, numItems) 68 69 // Immediately return half of the items, and construct a set of items 70 // consisting of the half that were not returned. 71 halfItemSet1 := returnN(t, bp, itemSet1, numItems/2) 72 73 // Wait long enough to ensure that adding subsequent items will not be 74 // released in the next gc cycle. 75 <-time.After(gcInterval - expiryInterval/2) 76 77 // Return the remaining items from itemSet1. 78 returnAll(bp, halfItemSet1) 79 80 // Wait until the gc cycle as done a sweep of the items and released all 81 // those that have expired. 82 <-time.After(expiryInterval / 2) 83 84 // Retrieve numItems items from the gc queue. 85 itemSet2 := takeN(t, bp, numItems) 86 87 // Tally the number of items returned from Take that are in the second 88 // half of items returned. 89 var numReused int 90 for item := range itemSet2 { 91 if _, ok := halfItemSet1[item]; ok { 92 numReused++ 93 } 94 } 95 96 // We expect the number of reused items to be equal to half numItems. 97 if numReused != numItems/2 { 98 t.Fatalf("expected %d items to be reused, got %d", 99 numItems/2, numReused) 100 } 101 } 102 103 // takeN draws n items from the provided GCQueue. This method also asserts that 104 // n unique items are drawn, and then returns the resulting set. 105 func takeN(t *testing.T, q *queue.GCQueue, n int) map[interface{}]struct{} { 106 t.Helper() 107 108 items := make(map[interface{}]struct{}) 109 for i := 0; i < n; i++ { 110 // Wait a small duration to ensure the tests behave reliable, 111 // and don't activate the non-blocking case unintentionally. 112 <-time.After(time.Millisecond) 113 114 items[q.Take()] = struct{}{} 115 } 116 117 if len(items) != n { 118 t.Fatalf("items taken from gc queue should be distinct, "+ 119 "want %d unique items, got %d", n, len(items)) 120 } 121 122 return items 123 } 124 125 // returnAll returns the items of the given set back to the GCQueue. 126 func returnAll(q *queue.GCQueue, items map[interface{}]struct{}) { 127 for item := range items { 128 q.Return(item) 129 130 // Wait a small duration to ensure the tests behave reliable, 131 // and don't activate the non-blocking case unintentionally. 132 <-time.After(time.Millisecond) 133 } 134 } 135 136 // returnN returns n items at random from the set of items back to the GCQueue. 137 // This method fails if the set's cardinality is smaller than n. 138 func returnN(t *testing.T, q *queue.GCQueue, 139 items map[interface{}]struct{}, n int) map[interface{}]struct{} { 140 141 t.Helper() 142 143 var remainingItems = make(map[interface{}]struct{}) 144 var numReturned int 145 for item := range items { 146 if numReturned < n { 147 q.Return(item) 148 numReturned++ 149 150 // Wait a small duration to ensure the tests behave 151 // reliable, and don't activate the non-blocking case 152 // unintentionally. 153 <-time.After(time.Millisecond) 154 } else { 155 remainingItems[item] = struct{}{} 156 } 157 } 158 159 if numReturned < n { 160 t.Fatalf("insufficient number of items to return, need %d, "+ 161 "got %d", n, numReturned) 162 } 163 164 return remainingItems 165 }