Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to use the logical call context within a unit test in VS 2010?

Is it possible to make this test not throw an exception? It appears that adding any non-GACed class into the logical call context causes an exception to be thrown in line 2 of the test.

Test 'TestProject1.UnitTest1.TestMethod1' failed: Test method TestProject1.UnitTest1.TestMethod1 threw exception: System.Configuration.ConfigurationErrorsException: An error occurred loading a configuration file: Type is not resolved for member 'TestProject1.Bar,TestProject1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. ---> System.Runtime.Serialization.SerializationException: Type is not resolved for member 'TestProject1.Bar,TestProject1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.

namespace TestProject1 {
    [ Serializable]
    public class Bar {

    }

    [TestClass]
    public class UnitTest1 {
        [TestMethod]
        public void TestMethod1() {
            CallContext.LogicalSetData("foo", new Bar());
            ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None );
        }
    }
}

WHY?!?!?

like image 758
aquinas Avatar asked Mar 26 '11 01:03

aquinas


3 Answers

Basically, this is a problem with the design of the process used to host the test code during a test run. Tests are run in a separate AppDomain from the test host process' default AppDomain. When a call is made from one AppDomain to another, the call context needs to be deserialized into the target AppDomain. In your case, ConfigurationManager.OpenExeConfiguration ends up calling AppDomain.get_Evidence. This results in a call from the AppDomain hosting the tests to the default AppDomain.

The default AppDomain's base directory is where the test host executable was installed to. By default, this is "%ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE". The AppDomain hosting the tests uses a base directory of the test run deployment location. By default, this is "<solutiondir>\TestResults\<testrun>\Out". Thus, the AppDomain used to run the tests can resolve the serialized types as their assemblies lie in the Out directory (regardless, they're already loaded into the test's AppDomain because this code executes) while the default AppDomain cannot.

GACing the assemblies containing the serialized types works because the GAC is probed during assembly resolution. This is one possible solution. However, you would need to install them to the GAC on every test run and this requires strong names and administrative privileges.

Another possible solution would be to copy the assemblies to the probing path of the default AppDomain. This would be the base directory listed above and the privatePaths listed in QTAgent.exe.config or QTAgent32.exe.config (which one runs depends on whether or not you're using a 64-bit operating system and the hosting platform setting in the test settings). As with all private probing paths, these directories must be sub-directories of the base directory. You could create a new sub-directory with the appropriate access permissions, add the directory name to the privatePaths in the .exe.config files, and then copy the assemblies containing the serialized types into this directory during test setup. This solution would not require administrative privileges or strong naming, provided the directory you're copying the assemblies to allows you to write to it.

Unfortunately, neither of these solutions are ideal as they can introduce the possibility for type mismatch between the code running in the tests' AppDomain and the default AppDomain if the assemblies are not updated properly before each test run. A proper fix would be required from Microsoft (say having the test host process automatically probe the test deployment directory when the default AppDomain needs to resolve types). I didn't work on the test tools, so I can't comment on what a real fix, taking implementation details into consideration, would require.

Edit: if you do elect one of these "solutions", you will also need to disable the test host process from staying alive between test runs. This is because the default AppDomain will stick around and the assemblies containing the serialized types will remain loaded, preventing you from updating them on the next test run. The option to control this is "Keep test execution engine running between test runs" under "Tools -> Options -> Test Tools -> Test Execution".

like image 185
Peter Huene Avatar answered Nov 04 '22 08:11

Peter Huene


This problem can be solved by having Bar implement MarshalByRefObject. This allows the class to be referenced in the AppDomain that the test runner is running inside.

[ Serializable]
public class Bar : MarshalByRefObject {

}
like image 35
CodeMonkeyKing Avatar answered Nov 04 '22 08:11

CodeMonkeyKing


When using logical CallContext to store objects implement the appropriate finalization logic (IDisposable) to clean up objects stored in the CallContext (e.g.: CallContext.FreeNamedDataSlot)

Hope this helps,

Juanjo

like image 31
jdearana Avatar answered Nov 04 '22 06:11

jdearana