Discussion:
[Mono-list] Unit tests load a different configuration file under mono
David Curylo
2013-08-09 22:24:13 UTC
Permalink
I ran into an issue several months back where my code could not find ConnectionStrings while running my code in NUnit under mono, however, I was having trouble making a simple reproducible test case. Now I have one, and I suspect that it is not an NUnit issue, as the same thing happens under xUnit. Rather, I'm guessing that this has to do with how the AppDomain in mono resolves configuration file locations in shadow copied assemblies like those used by unit testing frameworks.

My simple test is below. The first test will succeed, but the second one will fail only under mono (tried versions 2.10.8.1, 3.0.2, 3.1.2, and 3.2.0). Under MS.NET, both will succeed. The only difference in the first and the second test is that the second test uses HttpUtility from System.Web, and when this happens, the ConfigurationManager.ConnectionStrings changes to the default one from ASP.NET, containing a LocalSqlServer (SQLEXPRESS) and LocalSqliteServer.

Is anyone able to explain why this would happen only in the AppDomains as created by unit testing frameworks? Any known workarounds for this to keep it from loading a different config?

### TestCode.cs ###

using System;
using System.Configuration;
using NUnit.Framework;

namespace TestHarness
{
[TestFixture]
public class MyClass
{
[Test]
public void TestSomeCode1() {
var connStr = ConfigurationManager.ConnectionStrings["mystr"];
Assert.IsNotNull (connStr);
}
[Test]
public void TestSomeCode2() {
System.Web.HttpUtility.ParseQueryString ("http://foo.bz?bar=fu");
var connStr = ConfigurationManager.ConnectionStrings["mystr"];
Assert.IsNotNull (connStr);
}
}
}

### App.config ###

<configuration>
<connectionStrings>
<add name="mystr" connectionString="***@whatever.net" />
</connectionStrings>
</configuration>
Charlie Poole
2013-08-09 23:14:07 UTC
Permalink
Each test framework has it's own requirements for where a config file must
be located and how it should be named in order to be loaded and used. Those
requirements are necessarily different from how .NET normally locates
config files because following the .NET rules would load the config for the
test runner itself rather than for your tests.

In the case of NUnit, you can find the required info at
http://www.nunit.org/index.php?p=configFiles&r=2.6.2

Charlie
Post by David Curylo
I ran into an issue several months back where my code could not find
ConnectionStrings while running my code in NUnit under mono, however, I was
having trouble making a simple reproducible test case. Now I have one, and
I suspect that it is not an NUnit issue, as the same thing happens under
xUnit. Rather, I'm guessing that this has to do with how the AppDomain in
mono resolves configuration file locations in shadow copied assemblies like
those used by unit testing frameworks.
My simple test is below. The first test will succeed, but the second one
will fail only under mono (tried versions 2.10.8.1, 3.0.2, 3.1.2, and
3.2.0). Under MS.NET <http://ms.net/>, both will succeed. The only
difference in the first and the second test is that the second test uses
HttpUtility from System.Web, and when this happens, the
ConfigurationManager.ConnectionStrings changes to the default one from
ASP.NET <http://asp.net/>, containing a LocalSqlServer (SQLEXPRESS) and
LocalSqliteServer.
Is anyone able to explain why this would happen only in the AppDomains as
created by unit testing frameworks? Any known workarounds for this to keep
it from loading a different config?
### TestCode.cs ###
using System;
using System.Configuration;
using NUnit.Framework;
namespace TestHarness
{
[TestFixture]
public class MyClass
{
[Test]
public void TestSomeCode1() {
var connStr = ConfigurationManager.ConnectionStrings["mystr"];
Assert.IsNotNull (connStr);
}
[Test]
public void TestSomeCode2() {
System.Web.HttpUtility.ParseQueryString ("http://foo.bz?bar=fu<http://foo.bz/?bar=fu>
");
var connStr = ConfigurationManager.ConnectionStrings["mystr"];
Assert.IsNotNull (connStr);
}
}
}
### App.config ###
<configuration>
<connectionStrings>
</connectionStrings>
</configuration>
_______________________________________________
http://lists.ximian.com/mailman/listinfo/mono-list
Dave Curylo
2013-08-10 01:25:24 UTC
Permalink
Thanks for the reply. I understand how to get the test framework to load a
config file, and the first test does just that. It succeeds on both
platforms. The second test does the same exact thing, only it makes a call
to HttpUtility first, and this seems to trip up the ConfigurationManager,
but only under mono. Under MS .NET, both tests succeed in loading the
configuration and both pass, but under mono, the first test passes and the
second test fails because once a call is made to HttpUtility, the
ConnectionStrings are replaced with the defaults from the ASP.NET stack.
This seems like very odd behavior.
Post by Charlie Poole
Each test framework has it's own requirements for where a config file must
be located and how it should be named in order to be loaded and used.
Andrés G. Aragoneses
2013-08-10 08:40:06 UTC
Permalink
This is not related to NUnit. This is exactly bug 11972 which I filed
here some months ago: https://bugzilla.xamarin.com/show_bug.cgi?id=11972

The problem lies in the fact that whenever any code inside the
System.Web assembly tries to read the configuration, it replaces the
System.Configuration infrastructure to the one that is used when a
ASP.NET app is run.

I have a patch that fixes it, which I proposed in a pull request:
https://github.com/mono/mono/pull/725 . However, some time later after I
wrote the fix I realized it was based on false assumptions, and I
haven't had yet the time to investigate more. Do you mind reading the
pull request, subscribe to it, and help?

The approach I'm thinking now that may be correct would be replacing all
config queries in System.Web that use WebConfigurationManager, to start
using ConfigurationManager instead. But I haven't tested it yet.
Post by Dave Curylo
Thanks for the reply. I understand how to get the test framework to load
a config file, and the first test does just that. It succeeds on both
platforms. The second test does the same exact thing, only it makes a
call to HttpUtility first, and this seems to trip up the
ConfigurationManager, but only under mono. Under MS .NET, both tests
succeed in loading the configuration and both pass, but under mono, the
first test passes and the second test fails because once a call is made
to HttpUtility, the ConnectionStrings are replaced with the defaults
from the ASP.NET <http://ASP.NET> stack. This seems like very odd behavior.
Each test framework has it's own requirements for where a config
file must be located and how it should be named in order to be
loaded and used.
_______________________________________________
http://lists.ximian.com/mailman/listinfo/mono-list
Andrés G. Aragoneses
2013-08-10 08:42:08 UTC
Permalink
Post by Andrés G. Aragoneses
...
https://github.com/mono/mono/pull/725 .
Wrong URL, I meant this one: https://github.com/mono/mono/pull/643
David Curylo
2013-08-12 16:15:19 UTC
Permalink
when running inside a unit test, Mono.Web.Util.SettingsMappingManager.Init() doesn't manage to make the _instance not null, because mapper.HasMappings ends up being false (and this doesn't happen when running inside an ASP.NET site).
Have you had any luck finding why this is? I've found configuration works as you would expect when running inside an ASP.NET site under mono, so I'm really confused as to why things work so differently under a unit test. I'm trying to recreate this in isolation from the unit testing framework itself, but I'm not sure exactly what I should be doing - different AppDomain, shadow copied assemblies, but must be some other part of this scenario.
Wrong URL, I meant this one: https://github.com/mono/mono/pull/643
David Curylo
2013-08-12 22:22:26 UTC
Permalink
I've looked quite a bit more into this today. It can be reproduced simply with this (nothing to do with unit tests or shadow copies as you said, just happens when something in System.Web tries to read the configuration):

class MainClass
{
public static void Main (string[] args)
{
var dom1 = AppDomain.CreateDomain ("dom1");
var myCls = dom1.CreateInstanceAndUnwrap ("ConfigTesting", "ConfigTesting.MyClass") as MyClass;
myCls.ReadConfigSettingWeb();
}
}
class MyClass : MarshalByRefObject
{
public void ReadConfigSettingWeb() {
var c = System.Web.Util.HttpEncoder.Current;
var connStr = ConfigurationManager.ConnectionStrings["myconn"];
if(connStr == null)
throw new ConfigurationErrorsException("myconn is not found");
}

}

The statement "var c = System.Web.Util.HttpEncoder.Current" forces it to load the configuration, which works fine in the root AppDomain, but fails when run in a second AppDomain. After staring at profiler output all day and comparing with source, I am looking in the direction of the WebConfigurationHost.InitForConfiguration method. I suspect something here is redirecting the configuration when in an app domain like it would if it were in an ASP.NET virtual application, and if it can't find a config file in whatever path it's looking (erroneously) it will fallback on the machine.config. I haven't been able to get the runtime setup so I can step through all of this, but will do that soon.
Post by Andrés G. Aragoneses
Wrong URL, I meant this one: https://github.com/mono/mono/pull/643
_______________________________________________
http://lists.ximian.com/mailman/listinfo/mono-list
Loading...