Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference in code execution when extension method present but not called

TL;DR, The question:

What effect on the execution of code can the presence of an extension method have in .NET (e.g. JIT/optimizations)?

Background

I'm experiencing a test failure in MSTest that depends on whether a seemingly unrelated assembly is also tested.

I noticed the test failure and by accident noticed that the failure only occured if another test assembly was loaded. Running mstest on both the Unittests and Integration test assemblies would start executing the integration tests and fail on the 21st integration test under the 4.5 CLR, whereas this does not happen under the 4.0 CLR (same configuration otherwise). I removed all the tests but the failing one from the integration test assembly. Execution now looks like this with both test assemblies loaded, mstest loads both assemblies then executes the single test in the integration test assembly, which fails.

> mstest.exe /testcontainer:Unittests.dll /testcontainer:IntegrationTests.dll

Loading C:\Proj\Tests\bin\x86\Release\Unittests.dll...
Loading C:\Proj\Tests\bin\x86\Release\Integrationtests.dll...
Starting execution...

Results               Top Level Tests
-------               ---------------
Failed                Proj.IntegrationTest.IntegrationTest21

Without the Unittests assembly in the execution, the test passes.

> mstest.exe /testcontainer:IntegrationTests.dll

Loading C:\Proj\Tests\bin\x86\Release\Integrationtests.dll...
Starting execution...

Results               Top Level Tests
-------               ---------------
Passed                Proj.IntegrationTest.IntegrationTest21

I thought it must be an [AssemblyInitialize] thing being executed on the UnitTests dll, or perhaps a some sort of static state in the Unittest.dll or a common dependency being modified when the test assembly was loaded. I find neither any static constructors nor assembly init in the Unittests.dll. I suspected a deployment difference when the Unittests assembly was included, (dependent assembly deployed in different version etc.) but I compared the passing/failing deployment dirs and they are binary equivalent.

So what part of the Unittests assembly is causing the test difference? From the unit tests I removed half the tests at a time until I drilled it down to a source file in the Unit tests assembly. Along with the test class, an extension method is declared:

Apart from this extension class the Unittest assembly now contains a single test case in a dummy test class. The test failure occurs only if I have a dummy test method and the extension method declared. I could remove all of the remaining test logic until the Unittest dll is a single file, containing this:

// DummyTest.cs in Unittests.dll
[TestClass]
public class DummyTest
{
    [TestMethod]
    public void TestNothing()
    {
    }
}

public static class IEnumerableExtension
{
   public static IEnumerable<T> SymmetricDifference<T>(
       this IEnumerable<T> @this,         
       IEnumerable<T> that) 
   {
      return @this.Except(that).Concat(that.Except(@this));
   }
}

If either the test method or the extension class is removed, the test passes. Both present, and the test fails.

There are no calls to the extension method made from either assembly, and no code is executed in the Unittests assembly before the integration tests are executed (as far as I'm aware).

I'm sure the integration test is complex enough that JIT differences in optimization can cause a difference e.g. in floating point. Is that what I'm seeing?

like image 935
Anders Forsgren Avatar asked Feb 17 '14 09:02

Anders Forsgren


1 Answers

Probably the issue occurs due to type loading error.

When CLR runtime loads a class or method it always inspects all the types used in those items. It does not matter if type/method is actually called or not. What matters is the fact of declaration. Returning to your sample, the extension method SymmetricDifference declares that it uses Except and Concat methods from System.Core assembly.

What happens there is the error during loading of type System.Linq.Enumerable from System.Core assembly.

The reasons for that behavior can vary. The very first step to take is to log the exact exception you get on a test failure.

like image 123
ogggre Avatar answered Oct 27 '22 22:10

ogggre