plioi/fixie — pull request #68 by plioi (2014-12-11 19:55:17+00:00)
Title: Simplify AppDomain Interactions

The most difficult area in Fixie, historically, has been the need to run tests in a separate AppDomain from the runners' primary AppDomains. To help smooth over the most difficult parts of cross-AppDomain communication, the ExecutionEnvironment class was added. Runners (such as the console runner, visual studio runner, resharper runner, etc) have to make use of ExecutionEnvironment in order to discover and run tests, but before this pull request ExecutionEnvironment was still too complex and pushed too much responsibility on runner authors:

Before this pull request, the Visual Studio runner, for instance, had a snippet like this:

```csharp
var assemblyFullPath = Path.GetFullPath(assemblyPath);
var listener = new VisualStudioListener(frameworkHandle, assemblyPath);

using (var environment = new ExecutionEnvironment(assemblyFullPath))
{
var runner = environment.Create();
runner.RunAssembly(assemblyFullPath, options, listener);
}
```

Runners like this make their own Listener in order to receive events like "this test case passed" or "this test assembly completed". They create an ExecutionEnvironment to bottle up the AppDomain setup. They would call Create<T>() to create a proxy object to communicate through to the other side of the AppDomain boundary, and then call things like RunAssembly to actually get some work done.

There are a few flaws with this old API.

First, the runner author has to remember that ExecutionEnvironment assumed it would be given the full path to an assembly. Every single call to its constructor was preceded with the same Path.GetFullPath call.

Second, it was not clear to the runner author what types should be created through Create<T>(), or why you'd even want to in the first place. A design goal for ExecutionEnvironment was to minimize the amount of AppDomain knowledge that a runner author would have to be concerned with, and the need to decide what/when to proxy was leaking through the abstraction. Also, due to recent refactorings, there is now *only one* type you should ever create for proxying across the AppDomain boundary.

Third, the only things a runner author should do within an ExecutionEnvironment is Create an ExecutionProxy and call methods on *that*.

Fourth, the runner author has to pass the exact same path into the ExecutionEnvironment constructor that they pass into the ExecutionProxy methods.

This pull request fixes all these complications. ExecutionEnvironment's constructor can now take in relative paths. ExecutionEnvironment's Create method is now private, so only the intended proxy type ExecutionProxy is ever created for cross-AppDomain boundary communication, so a runner author won't be tempted to place the wrong type here like Runner. ExecutionEnvironment exposes methods 1-1 with ExecutionProxy. Since ExecutionEnvironment knows the assembly path, though, the Facade exposed by ExecutionEnvironment leaves out the redundant path argument, ensuring the right path is passed along to the proxy. With these improvements, the above snippet shortens to the following:

```csharp

var listener = new VisualStudioListener(frameworkHandle, assemblyPath);

using (var environment = new ExecutionEnvironment(assemblyPath))
{
var runner = environment.RunAssembly(options, listener);
}
```

Now, the only AppDomain concern pushed upon runner authors is that their Listener must subclass MarshalByRefObject, but that is a fundamental .NET concern outside of Fixie's control.