StackExchange.Precompilation - How can I unit test precompile?

Background

I use StackExchange.Precompilation to implement aspect-oriented programming in C #. Check out my repository on GitHub.

The main idea is that the client code will be able to place custom attributes for members, and the precompiler will perform syntactic conversions for any members with these attributes. A simple example is the NonNullAttribute that I created. When NonNullAttribute is placed in the p parameter, the precompiler will insert

 if (Object.Equals(p, null)) throw new ArgumentNullException(nameof(p)); 

at the beginning of the method body.


The diagnosis is amazing ...

I would like to make it difficult to use these attributes incorrectly. The best way I've found (other than intuitive design) is to create Diagnostic time for compilation for invalid or illogical attribute uses.

For example, NonNullAttribute does not make sense to use for the entered element values. (Even for null type values, because if you want to ensure that they are not null, you should use a non-null type instead.) Creating a Diagnostic is a great way to inform the user about this error without crashing the assembly as an exception.


... but how to check them?

Diagnostics is a great way to isolate errors, but I also want to make sure my diagnostic code has no errors. I would like to be able to set up a unit test, which can precompile a sample code like this

 public class TestClass { public void ShouldCreateDiagnostic([NonNull] int n) { } } 

and confirm that the correct diagnosis has been created (or in some cases when no diagnosis has been created).

Can anyone familiar with StackExchange.Precompilation give me some tips on this?


Decision:

The answer given by @ m0sa was incredibly helpful. There are many details to implement, so here is what unit test looks like (using NUnit 3). Pay attention to using static for SyntaxFactory , this eliminates a lot of noise in the construction of the syntax tree.

 using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using NUnit.Framework; using StackExchange.Precompilation; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace MyPrecompiler.Tests { [TestFixture] public class NonNull_CompilationDiagnosticsTest { [Test] public void NonNullAttribute_CreatesDiagnosticIfAppliedToValueTypeParameter() { var context = new BeforeCompileContext { Compilation = TestCompilation_NonNullOnValueTypeParameter(), Diagnostics = new List<Diagnostic>() }; ICompileModule module = new MyPrecompiler.MyModule(); module.BeforeCompile(context); var diagnostic = context.Diagnostics.SingleOrDefault(); Assert.NotNull(diagnostic); Assert.AreEqual("MyPrecompiler: Invalid attribute usage", diagnostic.Descriptor.Title.ToString()); //Must use ToString() because Title is a LocalizeableString } //Make sure there are spaces before the member name, parameter names, and parameter types. private CSharpCompilation TestCompilation_NonNullOnValueTypeParameter() { return CreateCompilation( MethodDeclaration(ParseTypeName("void"), Identifier(" TestMethod")) .AddParameterListParameters( Parameter(Identifier(" param1")) .WithType(ParseTypeName(" int")) .AddAttributeLists(AttributeList() .AddAttributes(Attribute(ParseName("NonNull")))))); } //Make sure to include Using directives private CSharpCompilation CreateCompilation(params MemberDeclarationSyntax[] members) { return CSharpCompilation.Create("TestAssembly") .AddReferences(References) .AddSyntaxTrees(CSharpSyntaxTree.Create(CompilationUnit() .AddUsings(UsingDirective(ParseName(" Traction"))) .AddMembers(ClassDeclaration(Identifier(" TestClass")) .AddMembers(members)))); } private string runtimePath = @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\"; private MetadataReference[] References => new[] { MetadataReference.CreateFromFile(runtimePath + "mscorlib.dll"), MetadataReference.CreateFromFile(runtimePath + "System.dll"), MetadataReference.CreateFromFile(runtimePath + "System.Core.dll"), MetadataReference.CreateFromFile(typeof(NonNullAttribute).Assembly.Location) }; } } 
+6
source share
1 answer

I suppose you want to add your diagnostics before the actual emit / compile, so the steps will be as follows:

  • create CSharpCompilation , make sure it has no diagnostic errors before moving on
  • create BeforeCompileContext and fill it with compilation and empty List<Diagnostic>
  • create an instance of your ICompileModule and call ICompileModule.BeforeCompile with the context from step 2
  • make sure it contains the required Diagnostic
+2
source

Source: https://habr.com/ru/post/1013141/


All Articles