Global AssertionOptions are not thread-safe
See original GitHub issueDescription
As discussed in #808 I’m opening a separate issue.
Global AssertionOptions appear to not be thread-safe.
Assume the following scenario.
Test 1 is using Should().BeEquivalentTo()
and the current execution is at CloneDefaults
Test 2 is executing AssertionOptions.AssertEquivalencyUsing()
at the same time on a different thread. This modifies the collection the CloneDefaults
is currently iterating through. Which is of course not allowed.
Complete minimal example reproducing the issue
Since this is a multi-threading issue it’s a bit difficult to reproduce. The more tests there are run in parallel, the higher the chance it will occur. But basically this is an outline of what happens in various tests that eventually lead to the exception. I’m trying to get a failure with only these 2 tests using Riders Run tests until failure
feature
namespace FluentAssertionsRepro
{
using FluentAssertions;
using Xunit;
public class Test1
{
[Fact]
public void Test()
{
const string Text = "Some text";
var container = new Container(Text);
container.Should().BeEquivalentTo(new Container(Text));
}
}
public class Test2
{
static Test2()
{
AssertionOptions.AssertEquivalencyUsing(
options => options
.Using<Container>(
context =>
{
context.Subject.Should().BeEquivalentTo(context.Expectation);
})
.WhenTypeIs<Container>());
}
[Fact]
public void Test()
{
const string Text = "Some text";
var x = new Container(Text);
x.Should().BeEquivalentTo(new Container(Text));
}
}
public class Container
{
public Container(string text) => this.Text = text;
public string Text { get; }
}
}
Expected behavior:
Have Test 1 run normally Have Test 2 run with updated AssertionOptions
Actual behavior:
I get this exception.
System.InvalidOperationException : Collection was modified; enumeration operation may not execute.
Stack Trace:
at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
at System.Linq.Enumerable.ConcatIterator`1.MoveNext()
at System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection)
at System.Collections.Generic.List`1.AddRange(IEnumerable`1 collection)
at FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions`1..ctor(IEquivalencyAssertionOptions defaults)
at FluentAssertions.Equivalency.EquivalencyAssertionOptions`1..ctor(IEquivalencyAssertionOptions defaults)
at FluentAssertions.AssertionOptions.CloneDefaults[T]()
at FluentAssertions.Primitives.ObjectAssertions.BeEquivalentTo[TExpectation](TExpectation expectation, Func`2 config, String because, Object[] becauseArgs)
at FluentAssertions.Primitives.ObjectAssertions.BeEquivalentTo[TExpectation](TExpectation expectation, String because, Object[] becauseArgs)
Versions
- Which version of Fluent Assertions are you using? 5.10.3
- Which .NET runtime and version are you targeting? E.g. .NET framework 4.6.1 or .NET Core 2.1. .NET Core 3.1 (netcoreapp3.1)
Additional Information
Any additional information, configuration or data that might be necessary to reproduce the issue.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:18 (17 by maintainers)
Top GitHub Comments
You could add an additional
if (alreadyLoaded) { return; }
before the lock, if it’s already true, nobody will set it to false and saves a lockI just tried fiddling with creating a custom
XunitTestFramework
and registering it using[assembly: TestFramework()]
.So far my experiments show that it:
XUnitTestProject3.zip