Serg046 Serg046 - 1 month ago 10
C# Question

Unit test for race condition

Consider the code:

class TestClass
{
private bool _someFlag;
private object _sharedObject = new object();
private readonly object _syncObject = new object();

public object Read()
{
//lock (_syncObject)
{
_someFlag = false;
return _sharedObject;
}
}

public void Write(object obj)
{
//lock (_syncObject)
{
_someFlag = true;
_sharedObject = obj;
}
}
}


It has race condition issue. When we call
Read()
some thread can call
Write()
between
_someFlag = false;
and
return _sharedObject;
lines. I am going to fix issue by
lock
operator. But could you please help me to unit test this race condition issue.

I do not want to change
_someFlag
to
public
for test purpose or anything like that.
I want to do something like that:

[Fact]
public void RaceConditionTest()
{
var correctObject = new object();
var test = new TestClass();

for (int i = 0; i < 1000; i++)
{
test.Write(correctObject);
var assertTask = Task.Run(() =>
{
var actualObj = test.Read();
Assert.True(object.ReferenceEquals(correctObject, actualObj), $"Failed on {i} iteration");
});
//Thread.Sleep(50);
var failTask = Task.Run(() => test.Write(new object()));

Task.WaitAll(assertTask, failTask);
}
}


But how can I be sure that
assertTask
will be started before
failTask
? Or maybe there is another way to unit test this case? Thanks in advance.

Answer

I have stayed with this approach. But still looking for a better way... This test fails on some iteration, but if you uncomment lock operators the test will be passed.

class TestClass
{
    private IEventRecorder _eventRecorder;


    private bool _someFlag;
    private object _sharedObject = new object();
    private readonly object _syncObject = new object();

#if DEBUG
    public void SetEventRecorder(IEventRecorder eventRecorder) => _eventRecorder = eventRecorder;
#endif

    public object Read()
    {
        //lock (_syncObject)
        {
#if DEBUG
            _eventRecorder?.Record(nameof(Read));
#endif
            _someFlag = false;
            return _sharedObject;
        }
    }

    public void Write(object obj)
    {
        //lock (_syncObject)
        {
#if DEBUG
            _eventRecorder?.Record(nameof(Write));
#endif
            _someFlag = true;
            _sharedObject = obj;
        }
    }

    public interface IEventRecorder
    {
        void Record(string eventName);
    }
}

public class TestClassTests
{
    private class EventRecorder : TestClass.IEventRecorder
    {
        private string _events = string.Empty;

        public void Record(string eventName) => _events += eventName;

        public string Events => _events;

        public void Reset() => _events = string.Empty;
    }

    [Fact]
    public void RaceConditionTest()
    {
        var correctObject = new object();
        var eventRecorder = new EventRecorder();
        var test = new TestClass();
        test.SetEventRecorder(eventRecorder);

        for (int i = 0; i < 1000; i++)
        {
            test.Write(correctObject);
            var assertTask = Task.Run(() =>
            {
                var actualObj = test.Read();
                if (eventRecorder.Events.StartsWith("WriteRead"))
                    Assert.True(object.ReferenceEquals(correctObject, actualObj), $"Failed on {i} iteration");
            });
            var failTask = Task.Run(() => test.Write(new object()));

            Task.WaitAll(assertTask, failTask);
            eventRecorder.Reset();
        }
    }
}