diff --git a/EasyAssertions/SourceExpressions/SourceExpressionProvider.cs b/EasyAssertions/SourceExpressions/SourceExpressionProvider.cs index c6b7a85..00b5a93 100644 --- a/EasyAssertions/SourceExpressions/SourceExpressionProvider.cs +++ b/EasyAssertions/SourceExpressions/SourceExpressionProvider.cs @@ -7,6 +7,7 @@ class SourceExpressionProvider : ITestExpressionProvider List stack = new(); int currentStackIndex; int lastStackIndex; + readonly object lockObject = new {}; static readonly AsyncLocal LocalInstance = new(); public static SourceExpressionProvider Current => LocalInstance.Value ??= new SourceExpressionProvider(); @@ -21,16 +22,19 @@ public void InvokeAssertion(Expression callAssertionMethod, string actua void TrackAssertion(Action assert, AssertionCall assertion, string actualSuffix, string expectedSuffix) { - try + lock (lockObject) { - EnterAssertion(assertion, actualSuffix, expectedSuffix); - assert(new AssertionContext()); - ExitAssertion(true); - } - catch - { - ExitAssertion(false); - throw; + try + { + EnterAssertion(assertion, actualSuffix, expectedSuffix); + assert(new AssertionContext()); + ExitAssertion(true); + } + catch + { + ExitAssertion(false); + throw; + } } } @@ -63,8 +67,17 @@ void ExitAssertion(bool success) lastStackIndex--; } - public string GetActualExpression() => NormalizeIndentation(LastAssertionFrame?.GetActualExpression() ?? string.Empty); - public string GetExpectedExpression() => NormalizeIndentation(LastAssertionFrame?.GetExpectedExpression() ?? string.Empty); + public string GetActualExpression() + { + lock (lockObject) + return NormalizeIndentation(LastAssertionFrame?.GetActualExpression() ?? string.Empty); + } + + public string GetExpectedExpression() + { + lock (lockObject) + return NormalizeIndentation(LastAssertionFrame?.GetExpectedExpression() ?? string.Empty); + } static string NormalizeIndentation(string input) { @@ -86,4 +99,12 @@ static string NormalizeIndentation(string input) AssertionFrame? CurrentAssertionFrame => stack.ElementAtOrDefault(currentStackIndex); AssertionFrame? LastAssertionFrame => stack.ElementAtOrDefault(lastStackIndex); + internal int CurrentStackIndex + { + get + { + lock(lockObject) + return currentStackIndex; + } + } } \ No newline at end of file diff --git a/UnitTests/SourceExpressionProviderTests.cs b/UnitTests/SourceExpressionProviderTests.cs index 8720717..4a373e4 100644 --- a/UnitTests/SourceExpressionProviderTests.cs +++ b/UnitTests/SourceExpressionProviderTests.cs @@ -269,6 +269,26 @@ public void ExpectedExpression_PassedThroughIndexedUserAssertion() Assert.AreEqual($"{nameof(expectedExpression)}[{expectedIndex}]", sut.GetExpectedExpression()); } + [Test] + public void MultiThreads_ShouldNotMessUpStack() + { + RunTasksParallel(200, TimeSpan.FromSeconds(30), InnerAssertionTask); + void InnerAssertionTask() + { + 0.ShouldBe(0); + Assert.AreEqual(0, sut.CurrentStackIndex); + } + void RunTasksParallel(int count, TimeSpan maxRunTime, Action action) + { + Task[] tasks = Enumerable.Range(0, count) + .Select(i => Task.Factory.StartNew(action)) + .ToArray(); + + Task.WaitAll(tasks, maxRunTime).ShouldBe(true); + Assert.AreEqual(0, sut.CurrentStackIndex); + } + } + class TestClass { public readonly int Value;