Jeff Griffin

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);
        }
    }
}

Tagged as: , No Comments