2016-12-14 15 views
6

TłoStackExchange.Precompilation - Jak mogę testować diagnostykę prekompilacji?

Używam StackExchange.Precompilation wdrożyć programowanie aspektowe w języku C#. See my repository on GitHub.

Podstawową ideą jest to, że kod klienta będzie umożliwiał umieszczanie niestandardowych atrybutów na elementach, a prekompilator będzie wykonywał transformacje składni dla dowolnych elementów o tych atrybutach. Prostym przykładem jest utworzony NonNullAttribute. Kiedy NonNullAttribute umieszcza się na parametrze p, Prekompilator wstawi

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

na początku treści metody.


Diagnostics są niesamowite ...

chciałbym utrudnić nieprawidłowo korzystać z tych atrybutów. Najlepszym sposobem, jaki znalazłem (poza intuicyjnym projektowaniem) jest stworzenie kompilacji dla nieprawidłowych lub nielogicznych zastosowań atrybutów.

Na przykład: NonNullAttribute nie ma sensu używać na elementach pisanych wartością. (Nawet w przypadku zerowych typów wartości, ponieważ jeśli chcesz zagwarantować, że nie były one zerowe, zamiast tego należy użyć typu nieululującego.) Tworzenie Diagnostic jest świetnym sposobem poinformowania użytkownika o tym błędzie, bez awarii kompilacji jak wyjątek.


... ale jak mogę je przetestować?

Diagnostyka to świetny sposób na podkreślanie błędów, ale chcę się upewnić, że mój kod diagnostyczny nie zawiera błędów. Chciałbym, aby móc założyć testów jednostkowych, które mogą precompile próbkę kodu, jak to

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

i potwierdzić, że tworzony jest poprawna diagnostyczny (lub w niektórych przypadkach, że nie zostały stworzone diagnostyka).

Czy każdy znany z StackExchange.Precompilation daje mi wskazówki na ten temat?


Rozwiązanie:

Odpowiedź udzielona przez @ m0sa był bardzo pomocny. Jest wiele szczegółów dotyczących implementacji, więc wygląda to tak, jakby test jednostkowy rzeczywiście wyglądał (używając NUnit 3). Uwaga: using static dla SyntaxFactory, to usuwa wiele bałaganu w konstrukcji drzewa składni.

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

Odpowiedz

2

I rysunek chcesz dodać Ci diagnostykę przed faktycznym emitują/kompilacji, więc kroki byłoby:

  1. stworzyć swój CSharpCompilation, upewnij się, że nie ma błędów diagnostycznych przed pójściem dalej
  2. Utwórz obiekt BeforeCompileContext i zapełnij go kompilacją oraz pustym List<Diagnostic>
  3. Utwórz instancję swojego ICompileModule i wywołaj ICompileModule.BeforeCompile z kontekstem f rom krok 2
  4. sprawdzić, czy zawiera ona wymagana Diagnostic
+0

Dziękuję bardzo. Próbuję tego teraz i złoŜę raport. – JamesFaix