question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Global AssertionOptions are not thread-safe

See original GitHub issue

Description

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:closed
  • Created 3 years ago
  • Reactions:1
  • Comments:18 (17 by maintainers)

github_iconTop GitHub Comments

3reactions
eNeRGy164commented, May 8, 2020

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 lock

3reactions
jnyrupcommented, May 8, 2020

I just tried fiddling with creating a custom XunitTestFramework and registering it using [assembly: TestFramework()].

So far my experiments show that it:

  • is invoked exactly once,
  • before any tests are run and
  • lets tests run in parallel.

XUnitTestProject3.zip

Read more comments on GitHub >

github_iconTop Results From Across the Web

Any satisfactory approaches to unit testing thread safety in ...
I am looking at improving a package that I believe not to be threadsafe when its input is shared between multiple worker threads....
Read more >
The way how threading works in assertions
The way how threading works in assertions · 1) I know that infinite delay (##[2:$]) will spawn infinite threads, likewise, will the infinite ......
Read more >
A wild Thread appeared! Multithreaded Testing and Thread ...
AssertJ is a java assertion library that helps us to improve our tests also on their readability. We should keep in mind that...
Read more >
Reading 20: Thread Safety
It's not threadsafe when used with a mutable collection. ... Our first way of achieving thread safety is confinement. ... Avoid Global Variables....
Read more >
Advanced GoogleTest Topics
The “threadsafe” death test style was introduced in order to help mitigate the risks of testing in a possibly multithreaded environment. It trades...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found