Blair Conrad

Limit FakeItEasy extension scanning with a bootstrapper

As of version 1.18.0, a client-supplied bootstrapper can be used to determine which external assembly files are scanned during startup.

Last time, I talked about how FakeItEasy extension scanning had improved in version 1.13.0. While this change has dramatically improved startup times in many situations, we recently received a comment from one of our valued clients (and subsequently a pull request with a proposed solution), detailing a situation where startup was taking about 13 seconds, mostly due to a huge number of assemblies in the working directory. Disabling shadow copy creation by the test runner alleviated the pain, but the incident prompted a re-examination of the issue.

While disabling shadow copies should resolve most slow startup problems caused by excessive working directory assemblies, and it may improve performance in other ways, recommending this to clients has always felt like a bit of a dodge to me, essentially pushing the problem off to someone else. There was also the lingering fear that someone would come back with a reason why the shadow copies were necessary.

We wanted to provide FakeItEasy's clients with a little more control over the process of scanning for assemblies. So, we've implemented the originally-proposed bootstrapper solution.

Using a custom bootstrapper

By default, after scanning all FakeItEasy-referencing assemblies currently loaded in the AppDomain, FakeItEasy 1.18.0 will examine all DLLs in the working directory. This behaviour can be changed by including in the AppDomain a class that implements FakeItEasy.IBootstrapper. As I write, this is the only behaviour that the bootstrapper controls:

/// <summary>
/// Provides a list of assembly file names to scan for extension points, such as
/// <see cref="IDummyDefinition"/>s, <see cref="IArgumentValueFormatter"/>s, and 
/// <see cref="IFakeConfigurator"/>s.
/// </summary>
/// <returns>
/// A list of absolute paths pointing to assemblies to scan for extension points.
/// </returns>
IEnumerable<string> GetAssemblyFileNamesToScanForExtensions();</code></pre>

The best way to implement the interface is to **extend
`FakeItEasy.DefaultBootstrapper`**. This class defines the default
FakeItEasy setup behaviour, so using it as a base allows
clients to customize only those aspects of the initialization that
matter to them.

While any list of assembly files can be provided by
`GetAssemblyFileNamesToScanForExtensions`, I expect that most
extensions that are defined will already be loaded in the current
AppDomain, so the most common customization will be to disable
external assembly scanning, like so:

<pre><code class="csharp">public class NoExternalScanningBootstrapper : FakeItEasy.DefaultBootstrapper
{
    public override IEnumerable<string> GetAssemblyFilenamesToScanForExtensions()
    {
        return Enumerable.Empty<string>();
    }
}

Of course, if there were extensions defined in an external assembly file or two, the GetAssemblyFilenamesToScanForExtensions implementation could return the paths to just those assemblies.