@@ -291,49 +291,94 @@ public void GetExistingMetric_ArgumentOutOfRangeException_ShouldReturnNull()
291291
292292 Assert . NotNull ( getExistingMetricMethod ) ;
293293
294- // Create a custom list that will throw ArgumentOutOfRangeException
295- var metricsList = new TestMetricsList ( ) ;
296- metricsList . Add ( new MetricDefinition ( "TestMetric" , MetricUnit . Count , new List < double > { 1.0 } , MetricResolution . Default ) ) ;
294+ // Create a list that will throw ArgumentOutOfRangeException on indexer access
295+ // This directly tests the catch (ArgumentOutOfRangeException) block in GetExistingMetric
296+ var metricsList = new ThrowingList ( ) ;
297297
298- // Act - Call the private method via reflection, searching for a different key
299- // This will cause the method to iterate and hit the indexer that throws ArgumentOutOfRangeException
300- var result = getExistingMetricMethod . Invoke ( null , new object [ ] { metricsList , "NonExistentMetric" } ) ;
298+ // Act - Call the private method via reflection
299+ var result = getExistingMetricMethod . Invoke ( null , new object [ ] { metricsList , "TestMetric" } ) ;
301300
302301 // Assert - Should return null when ArgumentOutOfRangeException is caught
303302 Assert . Null ( result ) ;
303+
304+ // Additional verification - ensure our ThrowingList actually throws
305+ Assert . Equal ( 1 , metricsList . Count ) ; // Should return 1
306+ Assert . Throws < ArgumentOutOfRangeException > ( ( ) => _ = metricsList [ 0 ] ) ; // Should throw
304307 }
305308
306- /// <summary>
307- /// Custom list that throws ArgumentOutOfRangeException on indexer access
308- /// to simulate the race condition scenario
309- /// </summary>
310- private class TestMetricsList : List < MetricDefinition >
309+ [ Fact ]
310+ public async Task AddMetric_ExtremeRaceCondition_ShouldCoverArgumentOutOfRangeException ( )
311311 {
312- private bool _shouldThrow = false ;
312+ // This test is designed to create the exact timing conditions that would
313+ // trigger ArgumentOutOfRangeException in GetExistingMetric during real usage
313314
314- public new int Count
315- {
316- get
317- {
318- // Return 1 initially, but set flag to throw on indexer access
319- _shouldThrow = true ;
320- return base . Count ;
321- }
322- }
315+ // Arrange
316+ Metrics . ResetForTest ( ) ;
317+ Metrics . SetNamespace ( "TestNamespace" ) ;
318+ var exceptions = new List < Exception > ( ) ;
319+ var tasks = new List < Task > ( ) ;
323320
324- public new MetricDefinition this [ int index ]
321+ // Act - Create extreme race conditions with very tight timing
322+ for ( int i = 0 ; i < 50 ; i ++ )
325323 {
326- get
324+ var taskId = i ;
325+ tasks . Add ( Task . Run ( async ( ) =>
327326 {
328- if ( _shouldThrow )
327+ try
329328 {
330- // Throw ArgumentOutOfRangeException to simulate the race condition
331- // where collection was modified between Count check and indexer access
332- throw new ArgumentOutOfRangeException ( nameof ( index ) , "Simulated race condition" ) ;
329+ for ( int j = 0 ; j < 500 ; j ++ )
330+ {
331+ // Add the same metric key repeatedly to trigger GetExistingMetric
332+ Metrics . AddMetric ( "RaceConditionMetric" , 1.0 , MetricUnit . Count ) ;
333+
334+ // Create timing pressure with very short delays
335+ if ( j % 25 == 0 )
336+ {
337+ await Task . Delay ( 1 ) ; // Tiny delay to create timing windows
338+
339+ // Force flush by adding 100+ metrics
340+ for ( int k = 0 ; k < 101 ; k ++ )
341+ {
342+ Metrics . AddMetric ( $ "FlushForce_{ taskId } _{ k } ", 1.0 , MetricUnit . Count ) ;
343+ }
344+ }
345+ }
333346 }
334- return base [ index ] ;
335- }
336- set => base [ index ] = value ;
347+ catch ( Exception ex )
348+ {
349+ lock ( exceptions )
350+ {
351+ exceptions . Add ( ex ) ;
352+ }
353+ }
354+ } ) ) ;
355+ }
356+
357+ await Task . WhenAll ( tasks ) ;
358+
359+ // Assert - Should not have any unhandled exceptions
360+ foreach ( var ex in exceptions )
361+ {
362+ Console . WriteLine ( $ "Exception: { ex . GetType ( ) . Name } : { ex . Message } ") ;
363+ }
364+ Assert . Empty ( exceptions ) ;
365+
366+ // Cleanup
367+ CleanupMetrics ( ) ;
368+ }
369+
370+ /// <summary>
371+ /// Custom list that always throws ArgumentOutOfRangeException on indexer access
372+ /// to directly test the exception handling path in GetExistingMetric
373+ /// </summary>
374+ private class ThrowingList : List < MetricDefinition >
375+ {
376+ public new int Count => 1 ; // Return 1 so the for loop condition (i < metrics.Count) passes
377+
378+ public new MetricDefinition this [ int index ]
379+ {
380+ get => throw new ArgumentOutOfRangeException ( nameof ( index ) , "Simulated concurrent modification" ) ;
381+ set => throw new ArgumentOutOfRangeException ( nameof ( index ) , "Simulated concurrent modification" ) ;
337382 }
338383 }
339384
0 commit comments