3

I one of my xUnit tests, I was using following code to locate a matching type:

var type = System.Type.GetType (typeName, throwOnError: false);

where typeName looks like:

Epsitec.Lydia.EventStore.TestBinaryEventStore+SimpleEvent, Tests.Lydia.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=361fc18aa5d4142d

If the type did not match exactly (e.g. because the Version part of the fully qualified type name diverged), I used to get a System.IO.FileLoadException with previous versions of .NET 4.5.x. Since I got my latest Windows Update delivered, which coincidentally contains .NET 4.5.2, I no longer get an exception if I do not specify the exact same version.

I initially thought this was caused by a new behaviour of GetType in .NET 4.5.2, but this seems not to be the case. I did not find any information about this change in MSDN's documentation.

I tried replicating this behaviour in a project outside of an xUnit test, but then I get the expected behaviour (System.IO.FileLoadException gets thrown because the type does not match).

What am I missing here? Any idea as to how I should proceed with my investivations?

Additional information

I used Fuslogvw to investigate further. The type resolution does indeed fail as this trace shows:

*** Assembly Binder Log Entry  (01.04.2015 @ 09:11:19) ***

The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
Running under executable  C:\PROGRAM FILES (X86)\MICROSOFT VISUAL STUDIO 12.0\COMMON7\IDE\EXTENSIONS\O3TVLY23.2NC\PlugIns\CR_ExtUnitTestRunnerNet4.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: DisplayName = Tests.Lydia.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4681e18aa5df9116
 (Fully-specified)
LOG: Appbase = file:///C:/PROGRAM FILES (X86)/MICROSOFT VISUAL STUDIO 12.0/COMMON7/IDE/EXTENSIONS/O3TVLY23.2NC/PlugIns
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = C:\Users\Arnaud\AppData\Local\Temp\85454750-eabd-4a3b-a5f2-91f3b104eba4
LOG: AppName = 85454750-eabd-4a3b-a5f2-91f3b104eba4
Calling assembly : Tests.Lydia.Framework, Version=1.1.1514.0, Culture=neutral, PublicKeyToken=4681e18aa5df9116.
===
LOG: This bind starts in LoadFrom load context.
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load().
LOG: Using application configuration file: S:\git\rnd\lydia\Tests.Lydia.Framework\bin\Release\Tests.Lydia.Framework.dll.config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Post-policy reference: Tests.Lydia.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4681e18aa5df9116
LOG: GAC Lookup was unsuccessful.
LOG: Attempting download of new URL file:///C:/PROGRAM FILES (X86)/MICROSOFT VISUAL STUDIO 12.0/COMMON7/IDE/EXTENSIONS/O3TVLY23.2NC/PlugIns/Tests.Lydia.Framework.DLL.
LOG: Attempting download of new URL file:///C:/PROGRAM FILES (X86)/MICROSOFT VISUAL STUDIO 12.0/COMMON7/IDE/EXTENSIONS/O3TVLY23.2NC/PlugIns/Tests.Lydia.Framework/Tests.Lydia.Framework.DLL.
LOG: Attempting download of new URL file:///C:/PROGRAM FILES (X86)/MICROSOFT VISUAL STUDIO 12.0/COMMON7/IDE/EXTENSIONS/O3TVLY23.2NC/PlugIns/Tests.Lydia.Framework.EXE.
LOG: Attempting download of new URL file:///C:/PROGRAM FILES (X86)/MICROSOFT VISUAL STUDIO 12.0/COMMON7/IDE/EXTENSIONS/O3TVLY23.2NC/PlugIns/Tests.Lydia.Framework/Tests.Lydia.Framework.EXE.
LOG: Attempting download of new URL file:///S:/git/rnd/lydia/Tests.Lydia.Framework/bin/Release/Tests.Lydia.Framework.DLL.
LOG: Assembly download was successful. Attempting setup of file: S:\git\rnd\lydia\Tests.Lydia.Framework\bin\Release\Tests.Lydia.Framework.dll
LOG: Entering run-from-source setup phase.
LOG: Assembly Name is: Tests.Lydia.Framework, Version=1.1.1514.0, Culture=neutral, PublicKeyToken=4681e18aa5df9116
WRN: Comparing the assembly name resulted in the mismatch: Minor Version
ERR: The assembly reference did not match the assembly definition found.
ERR: Run-from-source setup phase failed with hr = 0x80131040.
LOG: Attempting download of new URL file:///S:/git/rnd/lydia/Tests.Lydia.Framework/bin/Release/Tests.Lydia.Framework/Tests.Lydia.Framework.DLL.
LOG: Attempting download of new URL file:///S:/git/rnd/lydia/Tests.Lydia.Framework/bin/Release/Tests.Lydia.Framework.EXE.
LOG: Attempting download of new URL file:///S:/git/rnd/lydia/Tests.Lydia.Framework/bin/Release/Tests.Lydia.Framework/Tests.Lydia.Framework.EXE.
LOG: All probing URLs attempted and failed.

Still more context

I am running the code from within an xUnit test which is also referencing multiple other libraries. Trying to duplicate this outside of the original solution still does not exhibit the issue.

How could a referenced assembly tweak GetType in such a way that it would suddenly ignore the version part of the assembly qualified type name?

Pierre Arnaud
  • 10,212
  • 11
  • 77
  • 108
  • 2
    That would be rather a serious bug, reported by nobody else. Extraordinary claims do require extraordinary evidence, a minimum repro that we can run ourselves is rather important to get help. Or at least the output displayed by Fuslogvw.exe when you log all binds so there is *something* to look at. – Hans Passant Mar 30 '15 at 09:07
  • I've tried extracting the logic which leads to this strange behaviour into an external solution and I have not (yet) been able to reproduce the issue. – Pierre Arnaud Apr 01 '15 at 07:05
  • 1
    How can you get an exception if `throwOnError: false`. It should (and from my experience has) always return `null` if not found. – leppie Apr 01 '15 at 07:32
  • If the assembly cannot be found, then `GetType` always thows `System.IO.FileLoadException` no matter how you set `throwOnError`. – Pierre Arnaud Apr 01 '15 at 07:59
  • @leppie, `throwOnError` inhibits `FileNotFoundException` (assembly missing) but not `FileLoadException` (assembly is here but could not be loaded) – Guillaume Apr 01 '15 at 08:09
  • What is returned by GetType ? `null` or a type from an assembly with a different version ? maybe xUnit register to AssemblyResolve/TypeResolve events ? – Guillaume Apr 01 '15 at 08:19
  • If I ask for class `Foo` in assembly of version `1.0.0.0` and my assembly has now version `1.1.0.0` then I'll get the type `Foo` of my `1.1.0.0` assembly. What drives me nuts is that I have not (yet) been able to reproduce the issue outside of my product solution. – Pierre Arnaud Apr 01 '15 at 08:21
  • xUnit does not seem to be using `TypeResolve` events. – Pierre Arnaud Apr 01 '15 at 08:24

2 Answers2

3

xUnit have code registering to AppDomain.AssemblyResolve event and ignoring version and signature :

    Assembly LoadAssembly(AssemblyName assemblyName)
    {
        var path = Path.Combine(folder, assemblyName.Name);
        return LoadAssembly(path + ".dll") ?? LoadAssembly(path + ".exe");
    }

https://github.com/xunit/xunit/blob/master/src/common/AssemblyHelper.cs

If your runner is using it that's probably why you have this issue only in unit tests.

Guillaume
  • 12,824
  • 3
  • 40
  • 48
  • Wow, that is pretty daft... Unit tests pass, production fails... Edit: I see you have to turn this on with `SuscribeResolve` :D Not too bad I guess. – leppie Apr 01 '15 at 08:24
  • In my case, I have not turned on `SubscribeResolve`. However, I have now found that the problem only arises if I start the test from the CodeRush UI in Visual Studio. So there might be some magic happening behind the scenes inside DevExpress CodeRush. – Pierre Arnaud Apr 01 '15 at 09:02
  • @Guillaume: thank you for pointing me in the right direction. The culprit seems to be CodeRush in this case. I'll open an issue with DevExpress. – Pierre Arnaud Apr 01 '15 at 09:08
  • Here is the minimal project showing the problem: https://github.com/epsitec/Bugs-TypeResolution – Pierre Arnaud Apr 01 '15 at 09:15
  • CodeRush issue: https://www.devexpress.com/Support/Center/Question/Details/T225972 – Pierre Arnaud Apr 01 '15 at 09:22
0

The issue is not related to xUnit or any of the other assemblies referenced by my project. I've been able to replicate the same behaviour in a minimalist project and the source of this strange behaviour seems to originate from the CodeRush (14.2.6.0) tool I use to interact with my tests.

I've opened an issue with DevExpress and I will update this answer when I get a reply from their support team.

Pierre Arnaud
  • 10,212
  • 11
  • 77
  • 108