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 }