Skip to content

Commit b92cce3

Browse files
committed
trying again to test the exception logic
1 parent 8658968 commit b92cce3

File tree

1 file changed

+75
-30
lines changed

1 file changed

+75
-30
lines changed

libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ConcurrencyIssueTest.cs

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)