Roslyn + NUnit
Posted on May 3, 2012
Last night I sat down to work on my most recent project (full details another time, I promise). What I needed was a custom NUnit test runner. It didn't need to use Roslyn necessarily, but a large part of the project already relies on it, and you gotta love being able to emit directly to a memory buffer. Admittedly it only implements a subset of the NUnit spec, and I do intend to enhance it, but I did it in only a few hours! Probably most of that time was investigating the spec and writing tests.
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Roslyn.Compilers.CSharp; using System.IO; using Roslyn.Compilers; using NUnit.Framework; namespace com.griffinscs.Roslyn.NUnit { public interface ITestResult { object Fixture { get; } MethodInfo Method { get; } Exception Result { get; } } public class TestRunner { private class TestResult : ITestResult { public object Fixture { get; internal set; } public MethodInfo Method { get; internal set; } public Exception Result { get; internal set; } } private Exception CaptureException(Action action) { try { action.Invoke(); } catch(TargetInvocationException e) { return e.InnerException; } return null; } private IEnumerable<ITestResult> RunTest(object fixture, MethodInfo method) { return method.GetCustomAttributes( typeof(TestAttribute), true).Cast<TestAttribute>().Select( attr => { return new TestResult() { Fixture = fixture, Method = method, Result = CaptureException(() => method.Invoke(fixture, null)) }; }).Concat( method.GetCustomAttributes(typeof(TestCaseAttribute), true).Cast<TestCaseAttribute>().Select( attr => { return new TestResult() { Fixture = fixture, Method = method, Result = CaptureException(() => method.Invoke(fixture, attr.Arguments)) }; } )); } private IEnumerable<ITestResult> TryRunFixture(Type type) { return type.GetCustomAttributes( typeof(TestFixtureAttribute), true).Cast<TestFixtureAttribute>().Select( attr=>type.GetConstructor(attr.TypeArgs).Invoke(attr.Arguments)).SelectMany( fixture=> type.GetMethods(BindingFlags.Instance | BindingFlags.Public).SelectMany( method=>RunTest(fixture, method))); } public IEnumerable<ITestResult> Run(Compilation compilation) { Assembly assembly; using (var emitStream = new MemoryStream()) { var emitResult = compilation.Emit(emitStream); if (emitResult.Diagnostics.Any(diag => diag.Info.Severity == DiagnosticSeverity.Error)) { return null; } emitStream.Flush(); try { assembly = Assembly.Load(emitStream.GetBuffer()); } catch (BadImageFormatException) { return null; } } return assembly.GetTypes().SelectMany(TryRunFixture); } } }
Leave a comment