程序集神秘地加载到新的 AppDomain 中

发布于 2024-08-10 14:37:06 字数 1805 浏览 3 评论 0原文

我正在测试一些代码,只要程序集加载到应用程序域中,这些代码就可以工作。对于单元测试(在 VS2k8 的内置测试主机中),我在每次测试之前启动一个新的、唯一命名的应用程序域,并认为它应该是“干净的”:

[TestInitialize()]  
public void CalledBeforeEachTestMethod()  
{  
 AppDomainSetup appSetup = new AppDomainSetup();  
 appSetup.ApplicationBase = @"G:\<ProjectDir>\bin\Debug";
 Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;  
 Evidence evidence = new Evidence( baseEvidence );  
 _testAppDomain = AppDomain.CreateDomain( "myAppDomain" + _appDomainCounter++, evidence, appSetup );  
}  

[TestMethod]
public void MissingFactoryCausesAppDomainUnload()
{
 SupportingClass supportClassObj = (SupportingClass)_testAppDomain.CreateInstanceAndUnwrap(
GetType().Assembly.GetName().Name,
typeof( SupportingClass ).FullName );  
 try  
 {  
  supportClassObj.LoadMissingRegistrationAssembly();  
  Assert.Fail( "Should have nuked the app domain" );  
 }  
 catch( AppDomainUnloadedException ) { }  
}

[TestMethod]
public void InvalidFactoryMethodCausesAppDomainUnload()
{
 SupportingClass supportClassObj = (SupportingClass)_testAppDomain.CreateInstanceAndUnwrap(
GetType().Assembly.GetName().Name,
typeof( SupportingClass ).FullName );  
 try  
 {  
  supportClassObj.LoadInvalidFactoriesAssembly();  
  Assert.Fail( "Should have nuked the app domain" );  
 }  
 catch( AppDomainUnloadedException ) { }  
}

public class SupportingClass : MarshalByRefObject
{
 public void LoadMissingRegistrationAssembly()  
 {  
  MissingRegistration.Main();  
 }  
 public void LoadInvalidFactoriesAssembly()  
 {  
  InvalidFactories.Main();  
 }  
}

如果每个测试单独运行,我发现它可以正常工作; appdomain 已创建,并且仅加载了少数预期的程序集。但是,如果连续运行多个测试,则每个 _testAppDomain 已经具有从所有先前测试加载的程序集。奇怪的是,这两个测试获得了具有不同名称的应用程序域。定义 MissingRegistration 和 InvalidFactories(两个不同的程序集)的测试程序集永远不会加载到单元测试的默认应用程序域中。谁能解释这种行为?

I'm testing some code that does work whenever assemblies are loaded into an appdomain. For unit testing (in VS2k8's built-in test host) I spin up a new, uniquely-named appdomain prior to each test with the idea that it should be "clean":

[TestInitialize()]  
public void CalledBeforeEachTestMethod()  
{  
 AppDomainSetup appSetup = new AppDomainSetup();  
 appSetup.ApplicationBase = @"G:\<ProjectDir>\bin\Debug";
 Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;  
 Evidence evidence = new Evidence( baseEvidence );  
 _testAppDomain = AppDomain.CreateDomain( "myAppDomain" + _appDomainCounter++, evidence, appSetup );  
}  

[TestMethod]
public void MissingFactoryCausesAppDomainUnload()
{
 SupportingClass supportClassObj = (SupportingClass)_testAppDomain.CreateInstanceAndUnwrap(
GetType().Assembly.GetName().Name,
typeof( SupportingClass ).FullName );  
 try  
 {  
  supportClassObj.LoadMissingRegistrationAssembly();  
  Assert.Fail( "Should have nuked the app domain" );  
 }  
 catch( AppDomainUnloadedException ) { }  
}

[TestMethod]
public void InvalidFactoryMethodCausesAppDomainUnload()
{
 SupportingClass supportClassObj = (SupportingClass)_testAppDomain.CreateInstanceAndUnwrap(
GetType().Assembly.GetName().Name,
typeof( SupportingClass ).FullName );  
 try  
 {  
  supportClassObj.LoadInvalidFactoriesAssembly();  
  Assert.Fail( "Should have nuked the app domain" );  
 }  
 catch( AppDomainUnloadedException ) { }  
}

public class SupportingClass : MarshalByRefObject
{
 public void LoadMissingRegistrationAssembly()  
 {  
  MissingRegistration.Main();  
 }  
 public void LoadInvalidFactoriesAssembly()  
 {  
  InvalidFactories.Main();  
 }  
}

If every test is run individually I find that it works correctly; the appdomain is created and has only the few intended assemblies loaded. However, if multiple tests are run in succession then each _testAppDomain already has assemblies loaded from all previous tests. Oddly enough, the two tests get appdomains with different names. The test assemblies that define MissingRegistration and InvalidFactories (two different assemblies) are never loaded into the unit test's default appdomain. Can anyone explain this behavior?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

云归处 2024-08-17 14:37:06

听起来正在发生的事情是程序集正在父 AppDomain 中加载。如果是这样,您的错误在于您如何在测试代码中的其他地方使用 _testAppDomain 的详细信息,现在是如何创建它的详细信息。

理想情况下,测试工具代码应在 AppDomain 中自行运行,然后在 AppDomain 中运行的方法应实际加载被测程序集。

下面是一个示例,说明如何阻止父 AppDomain 加载测试程序集:

void TestRunner()
{
  testProxy =
    (TestProxy)_testAppDomain.CreateInstanceAndUnwrap(
                      typeof(TestProxy).Assembly.FullName,
                      typeof(TestProxy).FullName)

  testProxy.RunTest(testAssembly, typeName);
}

public class TestProxy : MarshalByRefObject
{
  public void Runtest(string testAssembly, string typeName)
  {
    var testType = Assembly.Load(testAssembly).GetType(typeName);

    // run tests in testType using reflection or whatever
  }
}

但是,在您的特定情况下,您本身可能没有任何测试程序集,所以也许这不会'申请。

我还注意到您的 [TestInitialize] 评论说每个测试调用一次,但是 IIRC,文档说 Visual Studio 的测试框架在运行多个测试时每个类仅调用一次。我使用不同的框架,所以我不确定这一点。

更新

现在我可以看到您的其余代码,我可以看到您已经采取了合理的预防措施,不要在父 AppDomain 中加载程序集。你说这确实没有发生,我相信你。如果情况变得更糟,您可以尝试让您的 SupportingClass 调用另一个程序集,然后该程序集进行您的测试,但我真的认为这不会改变任何事情。

我确实为您提供了另一个理论:

我在某处(我认为是在博客上)读到,JIT 方法是根据包含一些程序集加载规则的签名在 AppDomain 之间缓存和重用的。我认为这将包括 ApplicationBase。

所以我的理论是,当 NET Framework 加载您的程序集时(或者加载您的 SupportingClass 时),它会扫描它可以使用的已 JIT 方法。当它找到先前的 JIT 方法时,它会在该 AppDomain 中“启用”它(因为没有更好的词),这会触发它所依赖的程序集的加载。

这可以解释为什么更改 ApplicationBase 使其工作:不会从缓存中重用 JIT 方法,并且由于该方法永远不会被调用,因此不会再次进行 JIT 处理,因此永远不会加载依赖程序集。

在我看来,您在改变 ApplicationBase 方面有一个很好的解决方法,或者如果 ApplicationBase 保持不变很重要,您可以尝试改变证据。我认为这会产生同样的效果。

It sounds like what's is happening is that the assemblies are getting loaded in the parent AppDomain. If so, your bug is in the details of how you use are using _testAppDomain elsewhere in your test code, now how you create it.

Ideally test harness code should run itself in the AppDomain, and then the method running in the AppDomain should do the actual loading of the assemblies under test.

Here is an example to give the idea of how to keep the parent AppDomain from ever loading your test assemblies:

void TestRunner()
{
  testProxy =
    (TestProxy)_testAppDomain.CreateInstanceAndUnwrap(
                      typeof(TestProxy).Assembly.FullName,
                      typeof(TestProxy).FullName)

  testProxy.RunTest(testAssembly, typeName);
}

public class TestProxy : MarshalByRefObject
{
  public void Runtest(string testAssembly, string typeName)
  {
    var testType = Assembly.Load(testAssembly).GetType(typeName);

    // run tests in testType using reflection or whatever
  }
}

In your particular situation, however, it may be that you don't really have any test assemblies per se, so maybe this doesn't apply.

I also noticed that your [TestInitialize] comment says it is called once per test, but IIRC, the docs say Visual Studio's test framework only calls this once per class when it is running multiple tests. I use a different framework, so I am not sure of this.

Update

Now that I can see the rest of your code, I can see you already are taking reasonable precautions not to load assemblies in the parent AppDomain. You say that is indeed not happening, and I believe you. If worse comes to worse, you could try having your SupportingClass call another assembly which then does your test, but I don't really think this would change anything.

I do have another theory for you:

I read somewhere (on a blog, I think) that JITed methods are cached and reused between AppDomains based on a signature that includes some of the assembly load rules. I assume this would include ApplicationBase.

So my theory is that when NET Framework loads your assembly (or maybe when it loads your SupportingClass), it is scanning for already-JITed methods it can use. When it finds the previously JITed method it "enables" it (for lack of a better word) in that AppDomain, which triggers a load of assembly it depends on.

This would explain why changin ApplicationBase makes it work: The JITed methods would not be reused from the cache, and since the method is never called it is not JITed again so the dependent assembly is never loaded.

It seems to me that you have a good workaround in varying the ApplicationBase, or you might try varying the Evidence if the ApplicationBase is important to keep the same. I would think this would have the same effect.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文