github.com/decred/dcrlnd@v0.7.6/pool/recycle_test.go (about)

     1  package pool_test
     2  
     3  import (
     4  	"bytes"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/decred/dcrlnd/buffer"
     9  	"github.com/decred/dcrlnd/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  }