machine/machine.specifications — pull request #185 by citizenmatt (2013-11-27 15:41:21+00:00)
Title: [WIP] Late bound ReSharper runner

Firstly, this PR isn't ready to be merged, it's still a work in progress.

## What does this do?

It's not version independence, but it could play it on TV.

This PR allows the ReSharper runner to late bind against the Machine.Specifications.dll in the project's directory when running specs, rather than use the one that's bound against the ReSharper plugin. I've briefly tested it against the current version (i.e. building from sources) and version 0.4.1. It doesn't work with 0.3.0, and I haven't tested anything in between, but I expect it to work.

## What's changed?

* The ReSharper plugin has been split out into 2 assemblies, one for the provider and one for the runner
* The ReSharper provider is mostly unchanged, and references the latest Machine.Specifications.dll (from source)
* The runner has its own project, and own assembly, which has minimal references - it DOES NOT have a reference to Machine.Spefcications.dll
* The provider references the new runner project to share the `RemoteTask` types
* The runner dynamically loads Machine.Specifcations.dll from the project's folder and instantiates a runner. It uses Castle.Core to create a dynamic instance of the project mspec's `ISpecificationRunListener` and dispatches the progress reports to ReSharper

## How does it work?

In detail:
* The runner dynamically loads the Machine.Specifications.dll that lives next to the specs assembly
* It then uses reflection to instantiate `AppDomainRunner` and maintains a reference to it as an `object`
* It uses compiled `Expression` tree lambdas to call into `AppDomainRunner` members from this object. It uses this to call `StartRun`, `RunMember` and `EndRun`
* It uses Castle.DynamicProxy to create a proxy instance of the project mspec's `ISpecificationRunListener`, which it passes to `AppDomainRunner` to act as the callback from mspec
* The proxy `ISpecificationRunListener` notifies a new `PerAssemblyRunListenerInterceptor` of method calls, which it then dispatches, and implements by calling ReSharper's notification APIs
* The parameters for the methods on the intercepted `ISpecificationRunListener` can't be strongly typed and are treated as `object`s. They are then wrapped in classes that use (cached) compiled expression trees to populate required parameters

## What's not done/caveats?

* The obvious caveats - reflection, compiled expression trees and dynamic proxies. Need to keep backwards compatibility in mind when making future changes
* To recognize the special delegates, the current Machine.Specifications.dll is checking for special attributes that weren't applied in previous versions. I'm currently working around this by using ReSharper's external annotations feature to retroactively apply those attributes to pre-compiled assemblies. It's a bit of a hacky solution
* The remoting calls between AppDomains to report progress cause .net to try and rebind to Machine.Specifications.dll, by name ("Machine.Specifications, version=0.4.1, ...") (I don't know why) and that doesn't automatically resolve, even though the assembly is in memory (again, I don't know why) and ReSharper helpfully adds an assembly resolve event handler that gets the event first and looks to see if the file exists in the plugin dir. Since the provider has its own Machine.Specifications.dll, it loads that, and kaboom - we're using two different versions and casts fail and it's game over. So, the runner lives in a private folder "runner", underneath the provider - since there's no mspec dll here, the resolution works correctly and all is good. However, the provider needs to setup an assembly resolver to load the runner from the "runner" folder. The worst thing here is the introduction of a post build step to put the runner into the new folder.

## Still to do

- [ ] Confirm approach. Are there any more caveats? Is this too scary/flaky/hacky?
- [ ] Where does this fit into the strategy? I see this as a tactical fix, rather than a proper implementation of version independence. Ideally, we shouldn't be resorting to reflection, compiled expression trees and dynamic proxies
- [ ] Decide on minimum supported version - currently 0.4.1. I think 0.3.0 support is going to be too expensive, for not enough payback
- [ ] Check minimum supported version and fail fast with useful error message
- [ ] Test with more versions
- [ ] Improve error handling. It's a little super duper happy path right now
- [ ] Support ReSharper 8.1, 7.1 dotCover 2.5 + 2.6
- [ ] Remove support for ReSharper 6
- [ ] Restructure source code to live in new file structure
- [ ] Distribute ReSharper 8.x and dotCover 2.6 on ReSharper gallery