So I'm trying to set up a project using the NDepend API for some metrics on my code (Which works nicely), however, when I attempt to run a testing framework (NUnit) over it, I'm getting TypeInitializationExceptions thrown.
Here is some code to reproduce the errors I'm getting:
Create a class library project, and reference the NDepend API dll at $NDependInstallPath$\lib\NDependAPI, setting copy local to false. Create a class as follows:
public class NDependProjectLoader
{
public void LoadAnNDependProject()
{
var provider = new NDependServicesProvider();
}
}
Create a second class library project in the solution that will be your Test class. Reference NUnit and the project you created that references the NDependAPI
[TestFixture]
public class NDependProjectLoader_Tests
{
[Test]
public void I_can_load_an_depend_project()
{
new NDependProjectLoader().LoadAnNDependProject();
}
}
Build, and run the test using your test runner of choice (I've tried Resharper's test runner and the NUnit GUI).
You will get a TypeInitializationException on the line var provider = new NDependServicesProvider();
Looking inside that TypeInitializationException shows that the root exception's message is:
"{"Could not load file or assembly 'NDepend.Platform.DotNet' or one of its dependencies. The system cannot find the file specified.":"NDepend.Platform.DotNet"}".
Adding the AssemblyResolverHelper from the NDepend.PowerTools sample project and calling it as described in the getting started guide doesn't change the behaviour.
I /assume/ that the problem is related to this statement in the NDepend API getting started guide at http://www.ndepend.com/api/NDepend.API_gettingstarted.html
"The program executable assembly must be generated into $NDependInstallPath$\"
In the case where the code is started from a test runner the executable will not be in the ndepend install path.
Calling NDependProjectLoader.LoadAnNDependProject() from a console application whose .exe is generated in $NDependInstallPath$\lib\ (note that the .exe seems to need to be generated in the \lib\ subfolder not in $NDependInstallPath$\ as stated in the getting started guide) does not produce the exception, which further points to this being the cause.
So, the question is, how does one work with the NDepend API from anything other than a console application? For example, here I want to do so from a test runner. Another obvious candidate is an IIS application. Am I missing something obvious?
Indeed the problem comes from assembly resolution.
You must tell the CLR where to find NDepend assemblies (i.e in the dir $NDependInstallPath$\Lib)
You have 2 choices to do this. Suppose $NDependInstallPath$ is "C:\NDepend" just for example:
Either edit AssemblyResolverHelper to resolve assemblies in "C:\NDepend\Lib"
Either create a App.Config file for your exe, add a <probing> element that refers to "C:\NDepend\Lib". When the CLR won't resolve an assembly, then it'll look at dir in such element
Related
I'm working on a tool that needs to extract the NUnit tests names that are contained in an assembly.
As I need the exact name of the tests when they are run, and NUnit's way of naming the tests is not straightforward, I use the NUnit TestEngine to explore the assembly.
Something like that :
Assembly testAssembly; //loaded somewhere else
using var testEngine = new TestEngine();
testEngine.WorkDirectory = Path.GetDirectoryName(testAssembly.Location);
var package = new TestPackage(testAssembly.Location);
using var runner = testEngine.GetRunner(package);
var testsFound = runner.Explore(new TestFilter(string.Empty));
I build the whole tool with one of our projects as test subject, and it works well.
But when I try it on other similar projects (.Net 6, NUnit test projects for a web API), I have the following error :
Unable to load one or more of the requested types.
Could not load file or assembly 'Microsoft.AspNetCore.Hosting.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. The system cannot find the file specified.
The tests run properly inside the Visual Studio, with VS test runner or Resharper.
The current directory is set to the bin folder.
Looking at the project, it seems that this is a implicit dependency of Microsoft.AspnetCore.Hosting (referenced by AppInsight)
But :
I don't see any version 6.0.0.
I have another similar project that loads properly and have the exact same dependencies
I can't find any Nuget package that has this version (highest is 2.2.0), so I can't force the reference.
The Assembly loads properly (with Assembly.LoadFrom()), it fails in NUnit, when it tries to call LoadTests
I can't find where this reference is needed and why one solution is working and others are not, while they are very similar !
I'm out of ideas to make it work, so I'm looking for suggestions.
Have you considered simply telling NUnit to produce the list of tests for you?
The --explore option followed by a file name creates XML output but without a following name it produces a readable list of names on the console. You can redirect the output to a file if that's what you want.
nunit3-console yourtests.dll --explore
UPDATE
Make sure you use the standard .NET Framework build of NUnit3-console with the above option, not the .NET 6.0 build. The standard runner creates whatever process is needed to properly explore the test assembly.
History: nUnit 3. I have tests with complex inheritance. A certain object is created within SetUp or OneTimeSetUp. Those methods are virtual. When this object is not closed, the leak occur.
Problem: The object is destroyed in TearDown or OneTimeTearDown, but those are only called when SetUp or OneTimeSetUp succeed. So when exception occurs somewhere within TearDown or OneTimeTearDown, the leak occurs. As I mentioned, there are multiple inheritance levels, so exception and critical object creation may happen in different classes, on the different stack frames.
What I want to do: I want to have ITestEventListenerto react on failure before the initialization finished and clean up the critical object.
What I tried: in my test assembly I created the class:
namespace My.Whatever.Tests.Web.Util
{
[Extension(EngineVersion = "3.4")]
public class NunitEventListener : ITestEventListener
{
public void OnTestEvent(string report)
{
Debug.WriteLine(report);
}
}
}
Then I tried running tests through
VS (nunit 3 test adapter)
nUnit console
None seem to load the extension.
Question: what am I doing wrong?
Sources of info: https://github.com/nunit/docs/wiki/Event-Listeners , https://github.com/nunit/docs/wiki/Writing-Engine-Extensions
The info about how extensions are located is found at https://github.com/nunit/docs/wiki/Engine-Extensibility#locating-addins which is linked from the second of the two references you mention.
Extensions are not searched for in your test assembly. We provided that in V2 for NUnit Addins as an easy way to test extensions, but it's a bit more complicated to do that for engine extensions. IMO it would be a good feature if we could make that work, but it involves making all extensions capable of being loaded and unloaded as new test assemblies are run. That's a major internal change to our extension service.
In the directory containing the engine assembly, you may find one or more files of type .addins. Whether there is one and how many and what they contain will depend on how you installed the runner and engine. That file has entries pointing to the extensions installed for that particular copy of the engine. See the above reference page for details.
In two cases, the location of extensions is more or less automatic, due to the presence of wild-card entries in the .addins file:
If you install the console runner using NuGet any extensions installed as nuget packages are found.
If you install the console runner using Chocolatey, any extensions installed by chocolatey are found.
In all other cases, I'm afraid that you would have to edit the .addins file manually.
In the specific case of the adapter, there is no .addins file and therefore no extensions are ever loaded. In theory, you could create such a file by hand and cause your extension to load, at least if the engine is installed in a directory to which you have access. That would be the case if you are using the nuget package. I suggest you first play with having your extension recognized under the console runner before trying this as it would involve additional complications.
BTW, not all third-party runners use the engine. If they are not using the engine at all, of course, it's not possible to provide extensions.
Update: I only just noticed your statement that TearDown and OneTimeTearDown are only run when SetUp orOneTimeSetUp` succeed. That's not a true statement. Both kinds of teardown only run if the corresponding setup is run, whether it succeeds or not. Of course, your teardowns have to be written to allow for the fact that the corresponding setup may not have run to completion, which can be tricky.
I have a SpecFlow test, which feature file and step definitions are in different projects. It passes when running inside Visual Studio, but fails when running from the command line using MsTest.
The output error message is:
No matching step definition found for the step. Use the following code to create one:
[Given(#"I am a member")]
public void GivenIAMAMember()
{
ScenarioContext.Current.Pending();
}
Test method threw exception:
System.IO.FileNotFoundException: Could not load file or assembly 'nunit.framework' or one of its dependencies. The system cannot find the file specified.WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].
Anyone could give me any ideas?
Thank you in advance!
Your steps dll is likely not being loaded into memory by MSTest as nothing is referencing it. In VS specflow ensures that it is loaded I believe.
You can force it to be loaded, which might help by doing something like this:
create a class in your steps dll.
in your project with the feature files create a partial class with the same name as the test class specflow generated
in this partial class add a field and set it to a new instance of the class from the steps dll.
This should force the steps dll to be loaded into the test process and so then the steps should be discoverable.
Please note I have not tested this and its just off the top of my head.
Good luck.
I am building an automation harness using C# and am trying to do the following:
Bootstrap the harness
Install the executable
Use a couple of DLLs that the executable lays down to establish a connection to the infrastructure that the exe connects to (large, complex software system)
The DLLs that I need to use are a few static classes that perform their own 'bootstrap' process to get connected to the rest of the servers in the test environment. What I have tried is creating a connection class that looks like this:
using CompanyName.Framework;
namespace TestNamespace{
public class ProductConnectorClass{
// Initialize a connection to the product and do stuff
public ProductConnectorClass(){
var connection = CompanyName.Framework.Initialize(...);
// Do stuff with the connection
connection.RunStuff();
}
}
}
The DLL that contains the CompanyName.Framework namespace is not accessible when the test framework is first started. It is copied via code from another class that looks lomething like this:
namespace TestNamespace{
public class TestRunnerClass{
public TestRunnerClass(){
// pseudo code here, so you get the idea:
CopyMsiToHost(msiRemotePath, msiLocalPath);
InstallMsi(msiLocalPath);
CopyDllsToTestHarnessDir();
ProductConnectorClass pcc = new ProductConnectorClass();
}
}
}
It is when the code hits the ProductConnectorClass pcc = new ProductConnectorClass(); line that I get an exception:
FileNotFoundException was unhandled
Could not load file or assembly 'CompanyName.Framework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=XXXXXXXXXXXXXXXX' or one of its dependencies. The system cannot find the file specified.
This puzzles me since I have set Specific Version: False on the DLL in my test project and I am operating under the assumption that .NET Lazy-loads DLLs (in my mind that means search for and load them at the time they are needed). At this point I am looking for a simple way to get a running .NET program to find a needed DLL that has been placed after the process started running.
Note 1: If I restart the process after the DLL is already in place, it loads fine. It looks like it is only scanning for DLLs on startup, which still puzzles me.
Note 2: This SO answer gives me some information about how DLLs are loaded, but doesn't fully answer my question. The synopsis is that if .NET has tried to load the DLL and it fails that it won't try to load it again. In my case I don't expect that a load attempt has been made on the DLL since the class where the DLL is referenced has not been instantiated yet.
From experiments in test projects that I have performed, it appears that I need to perform modularization of my code in order to get this to work. In no case am I able to reference a dll that does not currently exist, start my program then drop down the DLL.
Here are my steps to a solution:
Refactor my common classes and interfaces to their own DLL project in my Automation solution
Create another DLL project ('TestLogic') in my solution that uses the DLL I lay down after the program has started
When I need to use the DLL that is laid down after the program starts, I use reflection to load the TestLogic dll and perform the tests
For reference I did a google search for 'C# late load dll' and found this msdn social post with some helpful advice. Here is a copy/paste/modify of the most helpful reply from that thread:
Move all the code that references the DLLs that are installed after the test framework starts to a completely separate assembly. We'll call this "TestLogicAssembly".
Create a new assembly and define an interface. We'll call this "TestInterfaceAssembly".
Reference TestInterfaceAssembly in both your main DLL and your TestLogicAssembly.
Create a class in TestLogicAssembly that implements the interface created in TestInterfaceAssembly.
Don't reference TestLogicAssembly from your main application.
At runtime, check to see if the DLLs that are laid down as part of the install step of the test framework are actually installed. If they are, use Assembly.Load to load the TestLogicAssembly, then find the type that implements the interface defined in number 2. Use Activator.CreateInstance to create an instance of this type, and cast it to the interface you created.
What I hoped would be an hour or two of work has now turned into quite the debacle with no result in sight.
Problem: I am trying to serialise a copy of my NHibernate Configuration and store it in... the project that was used to generate it!
Current manual solution: I have a project Foo that
contains a series of DomainObject.Map.cs files
References an "nhconfig.bin" file for embedding as a resource.
Contains a static method FooGenerateNHConfig that create a Configuration object and serialise it to nhconfig.bin.
To generate it, I:
(first time only: create an empty nhconfig.bin that acts as placeholder).
Build Foo.
Call a unit test that calls FooGenerateNHConfig.
Rebuild Foo.
Deploy application.
I'm trying to automate this, and thought it would be a simple matter:
Create a project Foo.BuildSupport that referenced Foo.
Define a Task X in it which would call FooGenerateNHConfig.
Setup an AfterCompile target that would call X.
Unfortunately I'm now getting 2 errors.
Firstly, a somewhat odd exception:
FluentNHibernate.Cfg.FluentConfigurationException: An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
---> System.Runtime.Serialization.SerializationException: Unable to find assembly 'FluentNHibernate, Version=1.1.0.685, Culture=neutral, PublicKeyToken=8aa435e3cb308880'.
I think that's FluentNHibernate complaining that it can't find the FluentNHibernate assembly?
Once I run the Task from visual studio the first time, visual studio (devenv.exe) locks my Foo.BuildSupport.dll AND my Foo.exe (presumably because it just sees them as support libraries and not the actual build libraries), so I can't rebuild them. Generally, this is because vs assumes (and probably rightfully so) that the BuildSupport library is fairly static and does not rely
What's a good way to automate a process such as this? I only have some preliminary thoughts right now but the only thing I can think of is building an entirely seperate executable to be run by msbuild (saw a task to do the equivalent of this, can't find it now), or something rather involved involving a seperate appdomain and manually calling the function via reflection. But before I went further down this path, am I missing something easier and more obvious?
I had a problem very similar to this one. My problem had something to do with MSBuild deserializing Xml to classes contained in a third-party assembly. It could not resolve the assembly needed for the serialization to work, even though the assemblies were part of the project and it had no problem resolving them outside of serialization. Can't give a more technical description of the problem than that, but I found this bit of code that fixed the issue for me,
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
Assembly ayResult = null;
string sShortAssemblyName = args.Name.Split(',')[0];
Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly ayAssembly in ayAssemblies)
{
if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0])
{
ayResult = ayAssembly;
break;
}
}
return ayResult;
}
public Constructor()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
I think the reason this works for me is because my application resolves the Assemblies outside of serialization so I override the AssemblyResolution callout to point it back at the good Assemblies that it for some reason wont use on its own.
Hope this is useful!