views:

124

answers:

3

Can anyone help me explain how TimeProvider.Current can become null in the following class?

public abstract class TimeProvider
{
    private static TimeProvider current =
        DefaultTimeProvider.Instance;

    public static TimeProvider Current
    {
        get { return TimeProvider.current; }
        set
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }
            TimeProvider.current = value;
        }
    }

    public abstract DateTime UtcNow { get; }

    public static void ResetToDefault()
    {
        TimeProvider.current = DefaultTimeProvider.Instance;
    }
}

Observations

  • All unit tests that directly reference TimeProvider also invokes ResetToDefault() in their Fixture Teardown.
  • There is no multithreaded code involved.
  • Once in a while, one of the unit tests fail because TimeProvider.Current is null (NullReferenceException is thrown).
  • This only happens when I run the entire suite, but not when I just run a single unit test, suggesting to me that there is some subtle test interdependence going on.
  • It happens approximately once every five or six test runs.
  • When a failure occurs, it seems to be occuring in the first executed tests that involves TimeProvider.Current.
  • More than one test can fail, but only one fails in a given test run.

FWIW, here's the DefaultTimeProvider class as well:

public class DefaultTimeProvider : TimeProvider
{
    private readonly static DefaultTimeProvider instance =
        new DefaultTimeProvider();

    private DefaultTimeProvider() { }

    public override DateTime UtcNow
    {
        get { return DateTime.UtcNow; }
    }

    public static DefaultTimeProvider Instance
    {
        get { return DefaultTimeProvider.instance; }
    }
}

I suspect that there's some subtle interplay going on with static initialization where the runtime is actually allowed to access TimeProvider.Current before all static initialization has finished, but I can't quite put my finger on it.

Any help is appreciated.


FWIW I just threw

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

in the getter, and it consistently reports the same ID for all test cases in a test run, so the issue seems not related to threading.

+1  A: 

Are you sure your test runner is not using multiple threads to keep the test runner UI responsive.

Update

Printing out thread ids may help to diagnose this.

chibacity
Same thread IDs all the way through...
Mark Seemann
Basic sanity check - good to rule it out.
chibacity
+3  A: 

Based solely on this code, Current could be null based on it being set to null. This obviously isn't helpful to you.

Could you provide the code for the tests? If there's a test interdependence, it would be helpful for readers in order to provide any feedback.

In the mean time, possibly Jon Skeet's article on singletons might be helpful, since DefaultTimeProvider is effectively acting as a singleton: http://www.yoda.arachsys.com/csharp/singleton.html

Peter Ritchie
I can't dump 113 xUnit.net tests here, and if there's a test interdependence, I can't figure out which one causes it.
Mark Seemann
xUnit, can you easily convert them to VS unit tests and run them in the VS unit test runner to see if there's a difference? Maybe the R# test runner would be easier? That would tell you if it's a side-effect of the runner.Otherwise, I would throw some tracing output in there (especially in DefaultTimeProvider.get_Instance) to see where it's null in relation to other operations.
Peter Ritchie
I think you are up to something, because I just converted *from* MSTest, and didn't have the problem before. However, there's no issue with xUnit.net's own GUI runner, so the problem may be with TestDriven.net. I'll follow up with Jamie.
Mark Seemann
I'm marking this as the answer. Not that it completely explains what's going on, but the link provided at least teached my sometihing about static initialization I didn't know before (see my own answer on this page for more information).
Mark Seemann
+1  A: 

I may have a partial answer to this, thanks to the links provided by Peter Ritchie, although I can't fully explain what's going on. It would seem that there was some kind of race going on between static initilization of TimeProvider and DefaultTimeProvider. It may have to do with beforefieldinit.

Changing the implementation seems to have resolved the issue. If not, it has certainly made the race condition much rarer, to a point where I have yet to see it.

I changed initialization of TimeProvider to this:

public abstract class TimeProvider
{
    private static TimeProvider current;

    static TimeProvider()
    {
        TimeProvider.current = new DefaultTimeProvider();
    }

    //...
}

And DefaultTimeProvider simply to this:

public class DefaultTimeProvider : TimeProvider
{
    public override DateTime UtcNow
    {
        get { return DateTime.UtcNow; }
    }
}

There is now only one static initializer in play (TimeProvider) and since it's an explicit static constructor the class is not marked beforefieldinit.

This seems to have done the trick...

Mark Seemann