github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/tracer/internal/rpc_sent_cache_test.go (about)

     1  package internal
     2  
     3  import (
     4  	"sync"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/rs/zerolog"
     9  	"github.com/stretchr/testify/require"
    10  	"go.uber.org/atomic"
    11  
    12  	"github.com/onflow/flow-go/model/flow"
    13  	"github.com/onflow/flow-go/module"
    14  	"github.com/onflow/flow-go/module/metrics"
    15  	p2pmsg "github.com/onflow/flow-go/network/p2p/message"
    16  	"github.com/onflow/flow-go/utils/unittest"
    17  )
    18  
    19  // TestCache_Add tests the add method of the rpcSentCache.
    20  // It ensures that the method returns true when a new record is initialized
    21  // and false when an existing record is initialized.
    22  func TestCache_Add(t *testing.T) {
    23  	cache := rpcSentCacheFixture(t, 100, zerolog.Nop(), metrics.NewNoopCollector())
    24  	controlMsgType := p2pmsg.CtrlMsgIHave
    25  	messageID1 := unittest.IdentifierFixture().String()
    26  	messageID2 := unittest.IdentifierFixture().String()
    27  
    28  	// test initializing a record for an ID that doesn't exist in the cache
    29  	initialized := cache.add(messageID1, controlMsgType)
    30  	require.True(t, initialized, "expected record to be initialized")
    31  	require.True(t, cache.has(messageID1, controlMsgType), "expected record to exist")
    32  
    33  	// test initializing a record for an ID that already exists in the cache
    34  	initialized = cache.add(messageID1, controlMsgType)
    35  	require.False(t, initialized, "expected record not to be initialized")
    36  	require.True(t, cache.has(messageID1, controlMsgType), "expected record to exist")
    37  
    38  	// test initializing a record for another ID
    39  	initialized = cache.add(messageID2, controlMsgType)
    40  	require.True(t, initialized, "expected record to be initialized")
    41  	require.True(t, cache.has(messageID2, controlMsgType), "expected record to exist")
    42  }
    43  
    44  // TestCache_ConcurrentInit tests the concurrent initialization of records.
    45  // The test covers the following scenarios:
    46  // 1. Multiple goroutines initializing records for different ids.
    47  // 2. Ensuring that all records are correctly initialized.
    48  func TestCache_ConcurrentAdd(t *testing.T) {
    49  	cache := rpcSentCacheFixture(t, 100, zerolog.Nop(), metrics.NewNoopCollector())
    50  	controlMsgType := p2pmsg.CtrlMsgIHave
    51  	messageIds := unittest.IdentifierListFixture(10)
    52  
    53  	var wg sync.WaitGroup
    54  	wg.Add(len(messageIds))
    55  
    56  	for _, id := range messageIds {
    57  		go func(id flow.Identifier) {
    58  			defer wg.Done()
    59  			cache.add(id.String(), controlMsgType)
    60  		}(id)
    61  	}
    62  
    63  	unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "timed out waiting for goroutines to finish")
    64  
    65  	// ensure that all records are correctly initialized
    66  	for _, id := range messageIds {
    67  		require.True(t, cache.has(id.String(), controlMsgType))
    68  	}
    69  }
    70  
    71  // TestCache_ConcurrentSameRecordInit tests the concurrent initialization of the same record.
    72  // The test covers the following scenarios:
    73  // 1. Multiple goroutines attempting to initialize the same record concurrently.
    74  // 2. Only one goroutine successfully initializes the record, and others receive false on initialization.
    75  // 3. The record is correctly initialized in the cache and can be retrieved using the Get method.
    76  func TestCache_ConcurrentSameRecordAdd(t *testing.T) {
    77  	cache := rpcSentCacheFixture(t, 100, zerolog.Nop(), metrics.NewNoopCollector())
    78  	controlMsgType := p2pmsg.CtrlMsgIHave
    79  	messageID := unittest.IdentifierFixture().String()
    80  	const concurrentAttempts = 10
    81  
    82  	var wg sync.WaitGroup
    83  	wg.Add(concurrentAttempts)
    84  
    85  	successGauge := atomic.Int32{}
    86  
    87  	for i := 0; i < concurrentAttempts; i++ {
    88  		go func() {
    89  			defer wg.Done()
    90  			initSuccess := cache.add(messageID, controlMsgType)
    91  			if initSuccess {
    92  				successGauge.Inc()
    93  			}
    94  		}()
    95  	}
    96  
    97  	unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "timed out waiting for goroutines to finish")
    98  
    99  	// ensure that only one goroutine successfully initialized the record
   100  	require.Equal(t, int32(1), successGauge.Load())
   101  
   102  	// ensure that the record is correctly initialized in the cache
   103  	require.True(t, cache.has(messageID, controlMsgType))
   104  }
   105  
   106  // rpcSentCacheFixture returns a new *RecordCache.
   107  func rpcSentCacheFixture(t *testing.T, sizeLimit uint32, logger zerolog.Logger, collector module.HeroCacheMetrics) *rpcSentCache {
   108  	config := &rpcCtrlMsgSentCacheConfig{
   109  		sizeLimit: sizeLimit,
   110  		logger:    logger,
   111  		collector: collector,
   112  	}
   113  	r := newRPCSentCache(config)
   114  	// expect cache to be empty
   115  	require.Equalf(t, uint(0), r.size(), "cache size must be 0")
   116  	require.NotNil(t, r)
   117  	return r
   118  }