Skip to content

Commit 90840b3

Browse files
thomhurstclaude
andauthored
fix: Resolve flaky NotInParallel test by using ConcurrentDictionary (#1679)
The test NotInParallel_If_Any_Modules_Executing_With_Any_Of_Same_ConstraintKey was flaky due to two issues: 1. Using ConcurrentBag with TryTake() which removes an arbitrary item, not the specific module name that was added. This corrupted tracking when multiple modules were executing concurrently. 2. Missing [TUnit.Core.NotInParallel] attribute on the test class, which could cause interference when tests run in parallel and share static state. The fix replaces ConcurrentBag with ConcurrentDictionary to ensure each module correctly removes only its own entry from the tracking collection, matching the pattern used in NotInParallelTestsWithConstraintKeys. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e38e16b commit 90840b3

1 file changed

Lines changed: 16 additions & 13 deletions

File tree

test/ModularPipelines.UnitTests/NotInParallelTestsWithMultipleConstraintKeys.cs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55

66
namespace ModularPipelines.UnitTests;
77

8+
[TUnit.Core.NotInParallel]
89
public class NotInParallelTestsWithMultipleConstraintKeys : TestBase
910
{
10-
private static readonly ConcurrentBag<string> _executingModules = new();
11+
// Use ConcurrentDictionary instead of ConcurrentBag to ensure we remove the correct entry
12+
// ConcurrentBag.TryTake() removes an arbitrary item, which corrupts tracking
13+
private static readonly ConcurrentDictionary<string, bool> _executingModules = new();
1114
private static readonly ConcurrentBag<string> _violations = new();
1215

1316
[ModularPipelines.Attributes.NotInParallel("A")]
@@ -17,17 +20,17 @@ public class Module1 : Module<string>
1720
{
1821
var moduleName = GetType().Name;
1922

20-
_executingModules.Add(moduleName);
23+
_executingModules[moduleName] = true;
2124
await Task.Delay(50, cancellationToken);
2225

23-
if (_executingModules.Contains("Module2"))
26+
if (_executingModules.ContainsKey("Module2"))
2427
{
2528
_violations.Add($"{moduleName} started while Module2 was executing (both have 'A')");
2629
}
2730

2831
await Task.Delay(50, cancellationToken);
2932

30-
_executingModules.TryTake(out _);
33+
_executingModules.TryRemove(moduleName, out _);
3134
return moduleName;
3235
}
3336
}
@@ -39,22 +42,22 @@ public class Module2 : Module<string>
3942
{
4043
var moduleName = GetType().Name;
4144

42-
_executingModules.Add(moduleName);
45+
_executingModules[moduleName] = true;
4346
await Task.Delay(50, cancellationToken);
4447

45-
if (_executingModules.Contains("Module1"))
48+
if (_executingModules.ContainsKey("Module1"))
4649
{
4750
_violations.Add($"{moduleName} started while Module1 was executing (both have 'A')");
4851
}
4952

50-
if (_executingModules.Contains("Module3"))
53+
if (_executingModules.ContainsKey("Module3"))
5154
{
5255
_violations.Add($"{moduleName} started while Module3 was executing (both have 'B')");
5356
}
5457

5558
await Task.Delay(50, cancellationToken);
5659

57-
_executingModules.TryTake(out _);
60+
_executingModules.TryRemove(moduleName, out _);
5861
return moduleName;
5962
}
6063
}
@@ -66,17 +69,17 @@ public class Module3 : Module<string>
6669
{
6770
var moduleName = GetType().Name;
6871

69-
_executingModules.Add(moduleName);
72+
_executingModules[moduleName] = true;
7073
await Task.Delay(50, cancellationToken);
7174

72-
if (_executingModules.Contains("Module2"))
75+
if (_executingModules.ContainsKey("Module2"))
7376
{
7477
_violations.Add($"{moduleName} started while Module2 was executing (both have 'B')");
7578
}
7679

7780
await Task.Delay(50, cancellationToken);
7881

79-
_executingModules.TryTake(out _);
82+
_executingModules.TryRemove(moduleName, out _);
8083
return moduleName;
8184
}
8285
}
@@ -87,11 +90,11 @@ public class Module4 : Module<string>
8790
public override async Task<string?> ExecuteAsync(IModuleContext context, CancellationToken cancellationToken)
8891
{
8992
var moduleName = GetType().Name;
90-
_executingModules.Add(moduleName);
93+
_executingModules[moduleName] = true;
9194

9295
await Task.Delay(100, cancellationToken);
9396

94-
_executingModules.TryTake(out _);
97+
_executingModules.TryRemove(moduleName, out _);
9598
return moduleName;
9699
}
97100
}

0 commit comments

Comments
 (0)