我为ReSharper编写了xunit.net测试运行程序,在8.0版本中遇到了一个问题——我看到程序集无法在影子复制的AppDomain中加载。相同的代码和测试项目在7.1版本中运行良好。不过我不明白为什么。
当运行测试时,ReSharper生成一个可执行文件来加载我的插件。我使用xunit.net的API来创建一个启用了卷影复制的AppDomain。测试项目程序集被复制到卷影复制缓存中,并开始加载。它将依赖项复制到缓存中,并加载它-FakeItEasy的旧版本,它使用Assembly.LoadFile加载当前目录中的所有程序集,该目录是测试项目的bin\Debug文件夹。因此,FakeIseasy将这些程序集加载到"两者都不加载"上下文中。由于它使用LoadFile,因此它绕过卷影副本缓存,文件直接从bin\Debug文件夹加载。
在此之后,测试项目的依赖项无法加载,从而导致FileNotFoundException。Fusion绑定日志显示它试图加载它们,但它们没有在卷影复制缓存中被复制,并且加载失败。我不明白为什么。这是绑定失败:
LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using host configuration file:
LOG: Using machine configuration file from C:WindowsMicrosoft.NETFramework64v4.0.30319configmachine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///C:/temp/todonancy/TodoNancyTests/bin/Debug/Nancy.Testing.DLL.
LOG: Assembly download was successful. Attempting setup of file: C:temptodonancyTodoNancyTestsbinDebugNancy.Testing.dll
LOG: Entering download cache setup phase.
LOG: Assembly Name is: Nancy.Testing, Version=0.17.1.0, Culture=neutral, PublicKeyToken=null
ERR: Setup failed with hr = 0x80070003.
ERR: Failed to complete setup of assembly (hr = 0x80070003). Probing terminated.
如果我禁用卷影复制缓存,或者使用不使用LoadFile的FakeIseasy的新版本,一切都可以。然而,我不能责怪FakeIseasy的旧版本——我有用户在其他项目和程序集中看到同样的错误,所有这些都通过禁用卷影复制缓存来解决。
此外,这个场景确实适用于ReSharper 7.1——相同的插件代码和相同的测试项目。唯一的区别是主机应用程序,但我看不出它有什么不同——例如,没有订阅其他程序集解析事件处理程序。唯一真正的区别是7.1主机使用远程处理与Visual Studio应用程序进行通信,而8.0使用简单的TCP套接字。
有人知道为什么这在8.0版本中失败,但在7.1版本中运行吗?
编辑(2013年8月7日):
我通过一个简单的测试使它失败了:
[Fact]
public void Thing()
{
Assert.NotNull(Nancy.Bootstrapper.AppDomainAssemblyTypeScanner.Assemblies);
}
使用直接添加到项目中的Nancy类的副本(带有引用的ScanMode和AssemblyExtensions类)。该项目中唯一的其他内容是对xunit.dll和xunit.extensions.dll的引用。
它并不是每次都失败,它是令人沮丧的间歇性的,但我可以让它在尝试从bin\Debug文件夹加载测试程序集时,在对Assembly.ReflectionOnlyLoadFrom
的调用中抛出一个FileNotFoundException
。
以下是来自异常的融合日志:
Assembly manager loaded from: C:WindowsMicrosoft.NETFramework64v4.0.30319clr.dll
Running under executable C:Program Files (x86)JetBrainsReSharperv8.0BinJetBrains.ReSharper.TaskRunner.CLR4.MSIL.exe
--- A detailed error log follows.
=== Pre-bind state information ===
LOG: Where-ref bind. Location = C:UsersMattCodescratchWeirdXunitFailuresWeirdXunitFailuresbinDebugWeirdXunitFailures.dll
LOG: Appbase = file:///C:/Users/Matt/Code/scratch/WeirdXunitFailures/WeirdXunitFailures/bin/Debug
LOG: Initial PrivatePath = NULL
Calling assembly : (Unknown).
===
LOG: This is an inspection only bind.
LOG: No application configuration file found.
LOG: Using host configuration file:
LOG: Using machine configuration file from C:WindowsMicrosoft.NETFramework64v4.0.30319configmachine.config.
LOG: Attempting download of new URL file:///C:/Users/Matt/Code/scratch/WeirdXunitFailures/WeirdXunitFailures/bin/Debug/WeirdXunitFailures.dll.
ERR: Failed to complete setup of assembly (hr = 0x80070003). Probing terminated.
不幸的是,这条消息没有告诉我任何信息-文件://url是有效的,并且卷影副本缓存包含xunit.dll和WeirdXuitFailures.dll(测试项目)。此外,调试器中的Modules窗口显示,WeitdXuitFailures.dll已经从卷影副本缓存位置加载。
再说一次,真正奇怪的是,7.1跑者表现得很好。
编辑:
事实上,我只需调用就可以让它失败
[Fact]
public void Thing()
{
Assembly.ReflectionOnlyLoadFrom(@"C:UsersMattCodescratchWeirdXunitFailuresWeirdXunitFailuresbinDebugxunit.dll");
Assembly.ReflectionOnlyLoadFrom(@"C:UsersMattCodescratchWeirdXunitFailuresWeirdXunitFailuresbinDebugxunit.extensions.dll");
Assembly.ReflectionOnlyLoadFrom(@"C:UsersMattCodescratchWeirdXunitFailuresWeirdXunitFailuresbinDebugWeirdXunitFailures.dll");
}
这是项目dll和两个xunit dll。它仍然是间歇性的,但似乎在完全重建后最容易复制,尽管它在几次成功运行后仍然会发生(所以不是重建出了问题)
Phew。问题(自然)是ReSharper 8的行为发生了微妙的变化。
测试运行程序进程API有一个方法来告诉主ReSharper进程(即devenv.exe)用于测试运行的临时文件夹的位置-卷影副本缓存。这是因为测试运行程序进程通常无法删除缓存文件夹,因为它仍在使用中。然后,ReSharper会多次尝试为您删除文件夹,让这个过程有时间正常结束。
ReSharper 7.1将在测试运行结束时或在运行中止时删除此文件夹。
ReSharper 8会在您调用该方法后立即删除文件夹。nunit测试运行器在测试运行的结束时告诉ReSharper这件事。我是在开始时告诉它的。因此,在我的测试运行期间,ReSharper会很高兴地出现,并从卷影副本缓存中删除它所能删除的内容,使其看起来像是卷影副本高速缓存被正确破坏了。
我想我会提交一个错误:)