The first symptom I noticed was when some of my unit tests wouldn’t run inside Visual Studio 2017. This wasn’t a simple case where a particular test failed, rather the problem was that the test wouldn’t run at all.

The Output window indicated that something was causing the test process to abort part way through:

The active Test Run was aborted because the execution process exited 
unexpectedly. To investigate further, enable local crash dumps either 
at the machine level or for process vstest.executionengine.x86.exe. 
Go to more details: http://go.microsoft.com/fwlink/?linkid=232477

I confirmed this by running my command line build and checking the output:

+---------------------------------------+
|  Document.Factory.Features.Tests.dll  |
+---------------------------------------+

xUnit.net Console Runner (64-bit .NET 4.0.30319.42000)
  Discovering: Document.Factory.Features.Tests
  Discovered:  Document.Factory.Features.Tests
  Starting:    Document.Factory.Features.Tests

Process is terminated due to StackOverflowException.

Now we’re getting somewhere; we’ve confirmed that the problem exists outside Visual Studio (which makes it easier to reproduce) and that it’s caused by a StackOverflowException - we even know which assembly contains the faulty test(s).

Back in Visual Studio, I checked to see if any more diagnostic information was available and found something interesting …

[xUnit.net 00:00:06.3566900] Discovering: Document.Factory.Features.Tests
'vstest.discoveryengine.x86.exe' WCF communication channel unavailable. 
Active request will be aborted.

It looks as though at least part of the problem is that something goes wrong when discovering tests, well prior to actually running them.

Usefully, the command line runner for xUnit allows you to run the tests from just one specific class or method. Working through each of the test fixture classes, in turn, I found that SerializerRoundTripTests+WorkspaceItemSelector was the class with the problem:

xunit.console.exe Document.Factory.Features.Tests.dll -verbose -parallel none -class Document.Factory.Features.Tests.Persistence.SerializerRoundTripTests+WorkspaceItemSelector

Specifying -parallel none turns off parallel execution, making the test outcomes more predictable.

Interestingly, I found that every test in that fixture failed, pointing to a problem with the whole class, not just a single test.

Closely reviewing the source for the test fixture class SerializerRoundTripTests, I discovered a subtle bug in the constructor that caused infinite recursion, blowing out the stack and killing the test process. It seems that text fixture initialization occurs outside the reporting scope for xUnit, so just about any exception aborts the test run.

Predictably, the proximate cause of the bug was a recent change I’d made elsewhere in the project, removing some lazy instantiation.

The root cause of the bug was more fundamental - the constructor of my test fixture was doing too much. I’d forgotten - or simply ignored - the guideline that object constructors should do as little as possible, ideally nothing beyond parameter validation and member initialization.

Moving the heavy lifting out of the constructor and into appropriate private members resolved the problem.

Comments

blog comments powered by Disqus