Tuesday, July 31, 2012

Running NUnit against 32-bit, .NET 3.5 assemblies


I recently ran across a problem at work with the latest version of NUnit's GUI test runner and its VS2010 plug-in.

No matter what I tried, the test would either fail to load (see the dialog box below) or one of them would consistently throw the following exception:

System.IO.FileLoadException : Mixed mode assembly is built against version 'v2.0.50727' of the runtime and cannot be loaded in the 4.0 runtime without additional configuration information.”

The culprit turned out to be a set of referenced assemblies that still target the 32-bit, .NET 3.5 framework.

So, that's easy, right? Just recompile the referenced assemblies to target .NET 4 and upgrade the targeted platform to x64 in Visual Studio, right?

Not so right in this case.

That solution is great if you have control over the assemblies you reference. I did not and thus had to find a way to make them work.

Solution Part #1: Run the 32-bit version of NUnit.
This should seem like a no-brainer, except for one very important detail.

nunit.exe is  a 64-bit app. Like any installation of a 64-bit app on a 64-bit system, all the Windows shortcuts target the 64-bit executable.

However, unlike a 64-bit app, NUnit installs itself in the "Program Files (x86)" file folder.

Thankfully, the 32-bit nunit-x86.exe is included. Once I redirected all my shortcuts to this file, I was able to load the test assemblies.


Solution Part #2: Add the necessary runtime and startup elements to the NUnit config files.
Once all my tests loaded I thought I had it made. Everything started along swimmingly until one of my tests began to fail, and fail consistently, with the "FileLoadException" mentioned above.

This was a head-scratcher. Every other test passed without trouble; it was just one test. Nothing was out of the ordinary in terms of the assemblies referenced by its test class.

Further research indicated that users of the the System.Data.SQLite assembly frequently encountered this exception in .NET 4 environments (such as my test assemblies).

System.Data.SQLite was in my referenced assemblies. Given what I knew at the time about our code base, I knew that it was used in some situations, but not all. After examining my test closely and stepping through some of the referenced code with a decompiler, it turned out the SQLite assembly was indeed the culprit.

So why did the SQLite assembly cause problems while the other references had no issues? The answer is actually in the exception message:

"Mixed mode assembly is built ..."

What is a mixed mode assembly? Simply put, it's a code assembly that contains both non-native, managed code (the kind of code you would generally code in, like C#, C++, or C) and native x86 instructions (really low-level stuff).

A brief scour of the internet gave me a solution--replace the SQLite assembly with one specifically targeting .NET 4.0.

Sound familiar?

In this case, three strikes were against me. I attempted to run code
  1. in a .NET 4-targeted test project and .NET 4 environment
  2. with an assembly that contained native x86 instructions while on an x64 system
  3. which required assemblies targeting an earlier version of .NET.

Even worse, I had control over none of the target frameworks. Once you've established the target framework for your test projects, you can't downgrade it.

Thankfully, one of the posts on the Logos Code blog provides the solution. It’s specific to MbUnit, but the fix also works for NUnit as well. Just find the nunit-x86.exe.config file in the NUnit directory and make the changes as detailed in the post.

To really be cool, though, you’ll also want to apply the fix to the NUnit runner extension for Visual Studio. To do so:
  1. Download the extension if you haven’t  (Tools => Extension Manager => Online Gallery => Search ‘NUnit’ => Download).
  2. Close Visual Studio.
  3. Navigate to ‘C:\Users\%USERNAME%\AppData\Local\Microsoft\VisualStudio\10.0\Extensions\Tommi Laukkanen %2F BubbleCloud.org\Visual Nunit 2010\1.2.4’.
  4. Create two new config files, “Visual NUnitRunner.exe.config” and “Visual NUnitRunner32.exe.config” and save them in the above directory.
  5. Restart Visual Studio.

Not only will that help ensure backwards compatibility of your GUI test runner, it will help ensure it for the extension as well.