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.

001using System;
002using System.Collections.Generic;
003using System.Linq;
004using System.Reflection;
005using Roslyn.Compilers.CSharp;
006using System.IO;
007using Roslyn.Compilers;
008using NUnit.Framework;
009 
010namespace com.griffinscs.Roslyn.NUnit
011{
012    public interface ITestResult
013    {
014        object Fixture { get; }
015        MethodInfo Method { get; }
016        Exception Result { get; }
017    }
018 
019    public class TestRunner
020    {
021        private class TestResult : ITestResult
022        {
023 
024            public object Fixture
025            {
026                get;
027                internal set;
028            }
029 
030            public MethodInfo Method
031            {
032                get;
033                internal set;
034            }
035 
036            public Exception Result
037            {
038                get;
039                internal set;
040            }
041        }
042 
043        private Exception CaptureException(Action action)
044        {
045            try { action.Invoke(); }
046            catch(TargetInvocationException e)
047            {
048                return e.InnerException;
049            }
050            return null;
051        }
052 
053        private IEnumerable<ITestResult> RunTest(object fixture, MethodInfo method)
054        {
055            return method.GetCustomAttributes(
056                typeof(TestAttribute), true).Cast<TestAttribute>().Select(
057                attr =>
058                {
059                    return new TestResult()
060                    {
061                        Fixture = fixture,
062                        Method = method,
063                        Result = CaptureException(() => method.Invoke(fixture, null))
064                    };
065                }).Concat(
066                method.GetCustomAttributes(typeof(TestCaseAttribute), true).Cast<TestCaseAttribute>().Select(
067                attr =>
068                {
069                    return new TestResult()
070                    {
071                        Fixture = fixture,
072                        Method = method,
073                        Result = CaptureException(() => method.Invoke(fixture, attr.Arguments))
074                    };
075                }
076                ));
077        }
078 
079        private IEnumerable<ITestResult> TryRunFixture(Type type)
080        {
081            return type.GetCustomAttributes(
082                typeof(TestFixtureAttribute), true).Cast<TestFixtureAttribute>().Select(
083                attr=>type.GetConstructor(attr.TypeArgs).Invoke(attr.Arguments)).SelectMany(
084                fixture=> type.GetMethods(BindingFlags.Instance | BindingFlags.Public).SelectMany(
085                    method=>RunTest(fixture, method)));
086        }
087 
088        public IEnumerable<ITestResult> Run(Compilation compilation)
089        {
090            Assembly assembly;
091            using (var emitStream = new MemoryStream())
092            {
093                var emitResult = compilation.Emit(emitStream);
094                if (emitResult.Diagnostics.Any(diag => diag.Info.Severity == DiagnosticSeverity.Error))
095                {
096                    return null;
097                }
098                emitStream.Flush();
099                try { assembly = Assembly.Load(emitStream.GetBuffer()); }
100                catch (BadImageFormatException)
101                {
102                    return null;
103                }
104            }
105            return assembly.GetTypes().SelectMany(TryRunFixture);
106        }
107    }
108}

Tagged as: , No Comments