/ pool / recycle_test.go
recycle_test.go
  1  package pool_test
  2  
  3  import (
  4  	"bytes"
  5  	"testing"
  6  	"time"
  7  
  8  	"github.com/lightningnetwork/lnd/buffer"
  9  	"github.com/lightningnetwork/lnd/pool"
 10  )
 11  
 12  type mockRecycler bool
 13  
 14  func (m *mockRecycler) Recycle() {
 15  	*m = false
 16  }
 17  
 18  // TestRecyclers verifies that known recyclable types properly return to their
 19  // zero-value after invoking Recycle.
 20  func TestRecyclers(t *testing.T) {
 21  	tests := []struct {
 22  		name    string
 23  		newItem func() interface{}
 24  	}{
 25  		{
 26  			"mock recycler",
 27  			func() interface{} { return new(mockRecycler) },
 28  		},
 29  		{
 30  			"write_buffer",
 31  			func() interface{} { return new(buffer.Write) },
 32  		},
 33  		{
 34  			"read_buffer",
 35  			func() interface{} { return new(buffer.Read) },
 36  		},
 37  	}
 38  
 39  	for _, test := range tests {
 40  		t.Run(test.name, func(t *testing.T) {
 41  			// Initialize the Recycler to test.
 42  			r := test.newItem().(pool.Recycler)
 43  
 44  			// Dirty the item.
 45  			dirtyGeneric(t, r)
 46  
 47  			// Invoke Recycle to clear the item.
 48  			r.Recycle()
 49  
 50  			// Assert the item is now clean.
 51  			isCleanGeneric(t, r)
 52  		})
 53  	}
 54  }
 55  
 56  type recyclePoolTest struct {
 57  	name    string
 58  	newPool func() interface{}
 59  }
 60  
 61  // TestGenericRecyclePoolTests generically tests that pools derived from the
 62  // base Recycle pool properly are properly configured.
 63  func TestConcreteRecyclePoolTests(t *testing.T) {
 64  	const (
 65  		gcInterval     = time.Second
 66  		expiryInterval = 250 * time.Millisecond
 67  	)
 68  
 69  	tests := []recyclePoolTest{
 70  		{
 71  			name: "write buffer pool",
 72  			newPool: func() interface{} {
 73  				return pool.NewWriteBuffer(
 74  					gcInterval, expiryInterval,
 75  				)
 76  			},
 77  		},
 78  		{
 79  			name: "read buffer pool",
 80  			newPool: func() interface{} {
 81  				return pool.NewReadBuffer(
 82  					gcInterval, expiryInterval,
 83  				)
 84  			},
 85  		},
 86  	}
 87  
 88  	for _, test := range tests {
 89  		t.Run(test.name, func(t *testing.T) {
 90  			testRecyclePool(t, test)
 91  		})
 92  	}
 93  }
 94  
 95  func testRecyclePool(t *testing.T, test recyclePoolTest) {
 96  	p := test.newPool()
 97  
 98  	// Take an item from the pool.
 99  	r1 := takeGeneric(t, p)
100  
101  	// Dirty the item.
102  	dirtyGeneric(t, r1)
103  
104  	// Return the item to the pool.
105  	returnGeneric(t, p, r1)
106  
107  	// Take items from the pool until we find the original. We expect at
108  	// most two, in the event that a fresh item is populated after the
109  	// first is taken.
110  	for i := 0; i < 2; i++ {
111  		// Wait a small duration to ensure the tests are reliable, and
112  		// don't to active the non-blocking case unintentionally.
113  		<-time.After(time.Millisecond)
114  
115  		r2 := takeGeneric(t, p)
116  
117  		// Take an item, skipping those whose pointer does not match the
118  		// one we dirtied.
119  		if r1 != r2 {
120  			continue
121  		}
122  
123  		// Finally, verify that the item has been properly cleaned.
124  		isCleanGeneric(t, r2)
125  
126  		return
127  	}
128  
129  	t.Fatalf("original item not found")
130  }
131  
132  func takeGeneric(t *testing.T, p interface{}) pool.Recycler {
133  	t.Helper()
134  
135  	switch pp := p.(type) {
136  	case *pool.WriteBuffer:
137  		return pp.Take()
138  
139  	case *pool.ReadBuffer:
140  		return pp.Take()
141  
142  	default:
143  		t.Fatalf("unknown pool type: %T", p)
144  	}
145  
146  	return nil
147  }
148  
149  func returnGeneric(t *testing.T, p, item interface{}) {
150  	t.Helper()
151  
152  	switch pp := p.(type) {
153  	case *pool.WriteBuffer:
154  		pp.Return(item.(*buffer.Write))
155  
156  	case *pool.ReadBuffer:
157  		pp.Return(item.(*buffer.Read))
158  
159  	default:
160  		t.Fatalf("unknown pool type: %T", p)
161  	}
162  }
163  
164  func dirtyGeneric(t *testing.T, i interface{}) {
165  	t.Helper()
166  
167  	switch item := i.(type) {
168  	case *mockRecycler:
169  		*item = true
170  
171  	case *buffer.Write:
172  		dirtySlice(item[:])
173  
174  	case *buffer.Read:
175  		dirtySlice(item[:])
176  
177  	default:
178  		t.Fatalf("unknown item type: %T", i)
179  	}
180  
181  }
182  
183  func dirtySlice(slice []byte) {
184  	for i := range slice {
185  		slice[i] = 0xff
186  	}
187  }
188  
189  func isCleanGeneric(t *testing.T, i interface{}) {
190  	t.Helper()
191  
192  	switch item := i.(type) {
193  	case *mockRecycler:
194  		if isDirty := *item; isDirty {
195  			t.Fatalf("mock recycler still diry")
196  		}
197  
198  	case *buffer.Write:
199  		isCleanSlice(t, item[:])
200  
201  	case *buffer.Read:
202  		isCleanSlice(t, item[:])
203  
204  	default:
205  		t.Fatalf("unknown item type: %T", i)
206  	}
207  }
208  
209  func isCleanSlice(t *testing.T, slice []byte) {
210  	t.Helper()
211  
212  	expSlice := make([]byte, len(slice))
213  	if !bytes.Equal(expSlice, slice) {
214  		t.Fatalf("slice not recycled, want: %v, got: %v",
215  			expSlice, slice)
216  	}
217  }