Re: NUnit core assembly referencing

"Charlie Poole" <[email protected]> Mon, 14 Apr 2008 14:28:56 -0700
Newsgroups gmane.comp.windows.dotnet.nunit.devel
Message-ID <016b01c89e76$8d4d92e0$6401a8c0@ferrari>
Hi Gary,


> Writing addins that will work across multiple versions of NUnit was
> something I had
> been holding off till 3.0, since the manner of loading assemblies will
> change with
> that release. That said, it's reasonable to explore the problem now - if
> nothing
> else, it will tell us how to deal with it later.
> 
> Bear in mind that your config file changes only affect the primary
> AppDomain.
> CoreExtensions runs in the test AppDomain. However, NUnit actually forces
> the nunit.core and nunit.core.interfaces into the test AppDomain after the
> test domain is unable to load them. Therefore, it's (ironically) important
> that
> they NOT be available to the test appdomain.
 
I think I follow this, but could you explain it a bit more?: when you say
"test AppDomain after the test domain is unable..." do you mean the second
one to be the primary domain?
 
No. Sorry for not being clearer.
 
First some definitions - just in case...
 
The primary AppDomain is the one that NUnit itself runs in. It has an
AppBase pointing to wherever NUnit is being run from - usually
C:\ProgramFiles\NUnit 2.x.x\bin. It has a config file nunit.exe.config or
nunit-console.exe.config.
 
NUnit creates secondary ApplicationDomains, which we call test domains each
time it loads a set of tests. It may use one for
all the tests or one per assembly depending on your setttings. Each test
domain has an AppBase pointing to the direfcftory 
containing your assembly or nunit project. It has a configfile matching the
naming pattern that NUnit enforces for configs
or a custom config specified in the .nunit file.
 
As part of initializing a test domain, NUnit asks that domain to create
instances of several classes requiring the nunit.core
and nunit.core.interface assemblies. That gets us to the sentence you didn't
understand.
 
The test domain cannot  find the required assemblies because there is no
copy of them in any directories it uses. The requested
load fails. NUnit intercepts that failure and supplies the proper assembly.
FWIW, NUnit does this for addins as well, since they
are not locatable by the test domain either.
 
If, for some reason, loading nunit.core were in the test domain were to
succeed, then NUnit would not have a chance to
supply it. That would be bad because NUnit needs to control the version that
is loaded. That's why having tests reference
nunit.core, directly or indirectly is bad for NUnit.
 
So, I don't want to change NUnit's app.config as I don't want to load them
into the primary app domain. How would you load them into the test appdomain
- do you do a Assembly.LoadFrom(), and if so, can you set up stuff like
binding redirects?
 
NUnit uses LoadFrom in a few restricted cases, but mostly uses Load. The
solution to the problem lies in ascertaining
exactly where it's happening, as I suggested in my earlier note. I suspect -
but need to confirm - that it happens
when we try to cast the interface from 2.4.6 to 2.4.7 - not from where we
load the assembly.
 
> If you have your attributes
> in your addin assembly, rather than a separate assembly, you will break
> this mechanism. I'll assume you don't.
> 
 
I have my attributes in a separate assembly Taumuon.Maslina.dll (which users
reference), my addin assembly Taumuon.Maslina.Addin.dll ALSO references this
assembly, but the users test assembly doesn't. Does the fact that my addin
assembly references the attributes assembly mean that this mechanism is
broken?
 
No, that's not a problem, although it does mean your users will have to
recompile for version changes. That's why NUnit uses tedious
string comparisons to  identify attributes rather than looking for a
particular Type.


Without any App.Config, the failure I get is in AddinManager.Register() - I
get a FileLoadException on loading Taumuon.Maslina.Addin.dll when it tries
to call Assembly.GetExportedTypes(). It's unable to find NUnit.Core.dll
v2.4.6.0.
 
The call stack is:
 
> nunit.util.dll!NUnit.Util.AddinManager.Register(string path =
"D:\\FILES\\GAZSTUFF\\Rakija\\NUnit2.4.7\\NUnit-2.4.7-src\\src\\GuiRunner\\n
unit-gui-exe\\bin\\Debug2005\\addins\\Taumuon.Maslina.AddIn.dll") Line 60 +
0x9 bytes C#
  nunit.util.dll!NUnit.Util.AddinManager.RegisterAddins() Line 47 + 0x17
bytes C#
  nunit.util.dll!NUnit.Util.AddinManager.InitializeService() Line 84 + 0x7
bytes C#
  nunit.util.dll!NUnit.Util.ServiceManager.InitializeServices() Line 62 +
0x9 bytes C#
  nunit-gui-runner.dll!NUnit.Gui.AppEntry.Main(string[] args =
{Dimensions:[0]}) Line 70 + 0x10 bytes C#
  nunit.exe!NUnit.Gui.Class1.Main(string[] args = {Dimensions:[0]}) Line 21
+ 0x7 bytes C#
 
This failure is in the primary AppDomain - where NUnit checks what Addins
are available. So it's understandable
that your subsequent change fixes it.
 
If I change the App.Config as I mentioned, it actually manages to register
the addin, but then fails on loading my sample assembly that references
Taumuon.Maslina.dll and Taumuon.Maslina.Test.dll.
 
The callstack is:
 
> nunit.core.dll!NUnit.Core.CoreExtensions.InstallAddins() Line 153 + 0x13
bytes C#
  nunit.core.dll!NUnit.Core.CoreExtensions.InitializeService() Line 208 +
0x7 bytes C#
  nunit.core.dll!NUnit.Core.RemoteTestRunner.Load(NUnit.Core.TestPackage
package = {NUnit.Core.TestPackage}) Line 33 + 0x10 bytes C#
  [External Code] 
  nunit.util.dll!NUnit.Util.TestDomain.Load(NUnit.Core.TestPackage package =
{NUnit.Core.TestPackage}) Line 57 + 0x15 bytes C#
  nunit.util.dll!NUnit.Util.TestLoader.LoadTest(string testName = null) Line
541 + 0x1d bytes C#
 
nunit.uikit.dll!NUnit.UiKit.TestLoaderUI.OpenProject(System.Windows.Forms.Fo
rm owner = {NUnit.Gui.NUnitForm}, string testFileName =
"D:\\FILES\\GAZSTUFF\\Rakija\\Taumuon.Maslina.1.0.0.0\\Taumuon.Maslina.Sampl
e\\bin\\Debug\\Taumuon.Maslina.Sample.dll", string configName = null, string
testName = null) Line 82 + 0xd bytes C#
  nunit-gui-runner.dll!NUnit.Gui.NUnitForm.NUnitForm_Load(object sender =
{NUnit.Gui.NUnitForm}, System.EventArgs e = {System.EventArgs}) Line 1334 +
0x30 bytes C#

This is simply the first bit of code in the test domain that needs to load
your addin.
 
I hope this gives you some ideas? I don't mind rebuilding my addins against
each new version of NUnit for now though.
  
I was kind of settled on that solution as being most expedient, since we are
going to
change the whole way addins are loaded. The approach I described above is a
bit of a kluge, since it requires an initial failure in order to work, so
I'd like to get
rid of it. However, that's a fairly big internal change.
 
Charlie
 
> The failure in CoreExtensions is probably happening at the point where
> the implemented interface is compared to the actual interface. Can you
> get enough of a trace to see if that's so?
> 
> Charlie
> 

Thanks!
 
Gary


  _____  

Have you played Fishticuffs? Get fish-slapping on Messenger
<http://www.fishticuffs.co.uk>

-------------------------------------------------------------------------
This SF.net email is sponsored by the 2008 JavaOne(SM) Conference 
Don't miss this year's exciting event. There's still time to save $100. 
Use priority code J8TL2D2. 
http://ad.doubleclick.net/clk;198757673;13503038;p?http://java.sun.com/javaone

_______________________________________________
nunit-developer mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/nunit-developer