I've been having problems running unit tests with code coverage for a while now and I'd appreciate some pointers. If I run my unit tests with code coverage, the tests run, but then I get an error right at the end, when SD is interpreting the results from OpenCover to display in the code coverage tab:
SharpDevelop Version : 4.4.1.9729-7196a277
.NET Version : 4.0.30319.18444
OS Version : Microsoft Windows NT 6.1.7601 Service Pack 1
Current culture : English (United Kingdom) (en-GB)
Running under WOW6432, processor architecture: x86-64
Working Set Memory : 133836kb
GC Heap Memory : 27460kb
Unhandled exception
Exception thrown:
System.ArgumentException: An item with the same key has already been added.
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at ICSharpCode.CodeCoverage.CodeCoverageResults.AddAssembly(XElement reader)
at ICSharpCode.CodeCoverage.CodeCoverageResults.ReadResults(XContainer reader)
at ICSharpCode.CodeCoverage.CodeCoverageResults..ctor(XContainer reader)
at ICSharpCode.CodeCoverage.CodeCoverageTestRunner.ReadCodeCoverageResults()
at ICSharpCode.CodeCoverage.RunTestWithCodeCoverageCommand.CodeCoverageRunFinished(Object source, EventArgs e)
at System.EventHandler.Invoke(Object sender, EventArgs e)
at ICSharpCode.UnitTesting.TestRunnerBase.OnAllTestsFinished(Object source, EventArgs e)
at ICSharpCode.SharpDevelop.Util.ProcessRunner.OnProcessExited(Object sender, EventArgs e)
at System.Diagnostics.Process.OnExited()
at System.Diagnostics.Process.RaiseOnExited()
at System.Diagnostics.Process.CompletionCallback(Object context, Boolean wasSignaled)
at System.Threading._ThreadPoolWaitOrTimerCallback.WaitOrTimerCallback_Context(Object state, Boolean timedOut)
at System.Threading._ThreadPoolWaitOrTimerCallback.WaitOrTimerCallback_Context_f(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading._ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Object state, Boolean timedOut)
---- Recent log messages:
16:31:31.773 [20] DEBUG - Received command ReportEvent
16:31:31.774 [20] DEBUG - Received command ReportEvent
16:31:31.774 [20] DEBUG - Received command BuildDone
16:31:31.774 [20] INFO - Finished building ImageTools.UnitTests, success=True
16:31:31.774 [20] INFO - Start building
16:31:31.774 [14] INFO - Finished building , success=True
16:31:31.775 [1] DEBUG - ActiveContentChanged to ICSharpCode.SharpDevelop.Gui.ErrorListPad
16:31:33.193 [1] DEBUG - CompilerMessageView: Combined 6 appends.
16:31:33.194 [1] DEBUG - CompilerMessageView: Combined 2 appends.
16:31:33.203 [1] DEBUG - CompilerMessageView: Combined 2 appends.
16:31:35.006 [1] DEBUG - TestClass not found: SDE.ImageTools.NUnit.ColourTest.TestFixtureBase
16:31:35.007 [1] DEBUG - TestClass not found: SDE.ImageTools.NUnit.ColourTest.TestFixtureBase
16:31:35.007 [1] DEBUG - TestClass not found: SDE.ImageTools.NUnit.ColourTest.XmlSerializableItemTestBase`1
16:31:35.007 [1] DEBUG - TestClass not found: SDE.ImageTools.NUnit.ColourTest.XmlSerializableItemTestBase`1
16:31:35.007 [1] DEBUG - TestClass not found: SDE.ImageTools.NUnit.ColourTest.XmlSerializableItemTestBase`1
16:31:35.007 [1] DEBUG - TestClass not found: SDE.ImageTools.NUnit.ColourTest.XmlSerializableItemTestBase`1
16:31:35.007 [1] DEBUG - TestClass not found: SDE.ImageTools.NUnit.ColourTest.XmlSerializableItemTestBase`1
16:31:35.007 [1] DEBUG - TestClass not found: SDE.ImageTools.NUnit.ColourTest.XmlSerializableItemTestBase`1
16:31:35.029 [1] DEBUG - CompilerMessageView: Combined 2 appends.
16:31:35.030 [1] DEBUG - CompilerMessageView: Combined 3 appends.
16:31:35.177 [1] DEBUG - CompilerMessageView: Combined 2 appends.
16:31:35.177 [1] DEBUG - CompilerMessageView: Combined 2 appends.
16:31:35.178 [1] DEBUG - CompilerMessageView: Combined 2 appends.
16:31:35.284 [6] FATAL - UnhandledException caught
--> Exception:
System.ArgumentException: An item with the same key has already been added.
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at ICSharpCode.CodeCoverage.CodeCoverageResults.AddAssembly(XElement reader)
at ICSharpCode.CodeCoverage.CodeCoverageResults.ReadResults(XContainer reader)
at ICSharpCode.CodeCoverage.CodeCoverageResults..ctor(XContainer reader)
at ICSharpCode.CodeCoverage.CodeCoverageTestRunner.ReadCodeCoverageResults()
at ICSharpCode.CodeCoverage.RunTestWithCodeCoverageCommand.CodeCoverageRunFinished(Object source, EventArgs e)
at System.EventHandler.Invoke(Object sender, EventArgs e)
at ICSharpCode.UnitTesting.TestRunnerBase.OnAllTestsFinished(Object source, EventArgs e)
at ICSharpCode.SharpDevelop.Util.ProcessRunner.OnProcessExited(Object sender, EventArgs e)
at System.Diagnostics.Process.OnExited()
at System.Diagnostics.Process.RaiseOnExited()
at System.Diagnostics.Process.CompletionCallback(Object context, Boolean wasSignaled)
at System.Threading._ThreadPoolWaitOrTimerCallback.WaitOrTimerCallback_Context(Object state, Boolean timedOut)
at System.Threading._ThreadPoolWaitOrTimerCallback.WaitOrTimerCallback_Context_f(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading._ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Object state, Boolean timedOut)
16:31:35.285 [6] FATAL - Runtime is terminating because of unhandled exception.
---- Post-error application state information:
Installed 3rd party AddIns:
Workbench.ActiveContent: --> Exception thrown by the state getter:
System.InvalidOperationException: This operation can be called on the main thread only.
at ICSharpCode.SharpDevelop.Gui.WorkbenchSingleton.AssertMainThread()
at ICSharpCode.SharpDevelop.Gui.WpfWorkbench.get_ActiveContent()
at ICSharpCode.SharpDevelop.Gui.WorkbenchSingleton.<InitializeWorkbench>b__0()
at ICSharpCode.Core.ApplicationStateInfoService.GetCurrentApplicationStateInfo()
ProjectService.OpenSolution: [Solution: FileName=C:\Users\Simon\Documents\SD\SDE\Source\GifComponents\GifComponents.sln, HasProjects=True, ReadOnly=False]
ProjectService.CurrentProject: [CSharpProject: ImageTools]
So I downloaded the SD source code, and using the stack trace I tracked down the point where this exception is being thrown. It's reading Module nodes from the file OpenCover\coverage.xml in the unit test project folder, which is presumably created by OpenCover, and adding their hash attribute and ModuleName element to a dictionary, with the hash as the dictionary key. And looking at that coverage.xml file, it's pretty clear why the exception is being thrown - it contains two entries for my business logic project, both with the same hash:
<Module hash="76-FF-A4-ED-FA-92-BC-E6-06-AE-5A-90-93-5B-FA-D9-C2-FD-65-5B">
<FullName>C:\Users\Simon\AppData\Local\Temp
unit20\ShadowCopyCache\7328_635550354934239533\Tests_704519562\assembly\dl3\8b33b4af\b08bb4f5_961fd001\SDE.ImageTools.dll</FullName>
<ModuleName>SDE.ImageTools</ModuleName>
<Module hash="76-FF-A4-ED-FA-92-BC-E6-06-AE-5A-90-93-5B-FA-D9-C2-FD-65-5B">
<FullName>C:\Users\Simon\Documents\SD\SDE\Source\GifComponents\ImageTools.UnitTests\bin\Debug\SDE.ImageTools.dll</FullName>
<ModuleName>SDE.ImageTools</ModuleName>
And likewise, two entries for my unit test project:
<Module hash="CA-6E-3D-B2-A4-36-D2-3A-46-B1-51-70-73-16-32-C7-F1-04-55-CE">
<FullName>C:\Users\Simon\AppData\Local\Temp
unit20\ShadowCopyCache\7328_635550354934239533\Tests_704519562\assembly\dl3\53ae0dc4\754fcff6_961fd001\SDE.ImageTools.UnitTests.dll</FullName>
<ModuleName>SDE.ImageTools.UnitTests</ModuleName>
<Module hash="CA-6E-3D-B2-A4-36-D2-3A-46-B1-51-70-73-16-32-C7-F1-04-55-CE">
<FullName>C:\Users\Simon\Documents\SD\SDE\Source\GifComponents\ImageTools.UnitTests\bin\Debug\SDE.ImageTools.UnitTests.dll</FullName>
<ModuleName>SDE.ImageTools.UnitTests</ModuleName>
So in each case, it seems to be recording the dll file twice, once in the project output folder, and once in a shadow copy location.
I tried reproducing this problem with a new solution containing a business logic project with only one class and a unit test project with just one test, however I don't get the same error with that solution. Looking at the coverage.xml file for that solution, there's only one entry for each project:
<Module hash="DB-39-9C-F6-A3-71-13-06-D8-CD-5D-1E-F7-AE-0D-62-B5-E0-CD-17">
<FullName>C:\Users\Simon\AppData\Local\Temp
unit20\ShadowCopyCache\8384_635549652165979974\Tests_634242734\assembly\dl3\b40ad16d\86b93173_f31ed001\TestProject.dll</FullName>
<ModuleName>TestProject</ModuleName>
<Module hash="9C-DE-AB-80-F7-9F-73-11-C5-52-97-FD-E8-D2-57-21-FE-7F-85-BC">
<FullName>C:\Users\Simon\AppData\Local\Temp
unit20\ShadowCopyCache\8384_635549652165979974\Tests_634242734\assembly\dl3\7de45542\9420162f_f31ed001\ProjectOne.dll</FullName>
<ModuleName>ProjectOne</ModuleName>
And both point to the shadow copy location, not to the project's output folder. So what could be causing OpenCover to create the extra entry in this file, pointing to the project's output folder?