Skip to content

Commit

Permalink
Add testing for attribute discovery in cases where the user prefixes
Browse files Browse the repository at this point in the history
the attribute name.

Add test cases for compilation failures.
  • Loading branch information
AaronRobinsonMSFT committed Jul 28, 2020
1 parent 35c9adc commit d2f6f5a
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 37 deletions.
29 changes: 29 additions & 0 deletions DllImportGenerator/DllImportGenerator.Test/CodeSnippets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,35 @@ partial class Test
[GeneratedDllImport(""DoesNotExist"")]
public static partial void Method(int t = 0);
}
";

/// <summary>
/// Declaration with user defined attributes with prefixed name.
/// </summary>
public static readonly string UserDefinedPrefixedAttributes = @"
using System;
using System.Runtime.InteropServices;
namespace System.Runtime.InteropServices
{
// Prefix with ATTRIBUTE so the lengths will match during check.
sealed class ATTRIBUTEGeneratedDllImportAttribute : Attribute
{
public ATTRIBUTEGeneratedDllImportAttribute(string a) { }
}
}
partial class Test
{
[ATTRIBUTEGeneratedDllImportAttribute(""DoesNotExist"")]
public static partial void Method1();
[ATTRIBUTEGeneratedDllImport(""DoesNotExist"")]
public static partial void Method2();
[System.Runtime.InteropServices.ATTRIBUTEGeneratedDllImport(""DoesNotExist"")]
public static partial void Method3();
}
";
}
}
39 changes: 39 additions & 0 deletions DllImportGenerator/DllImportGenerator.Test/CompileFails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using Xunit;

namespace DllImportGenerator.Test
{
public class CompileFails
{
public static IEnumerable<object[]> CodeSnippetsToCompile()
{
yield return new object[] { CodeSnippets.UserDefinedPrefixedAttributes, 3 };
}

[Theory]
[MemberData(nameof(CodeSnippetsToCompile))]
public void ValidateSnippets(string source, int failCount)
{
Compilation comp = TestUtils.CreateCompilation(source);
TestUtils.AssertPreSourceGeneratorCompilation(comp);

var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator());
Assert.Empty(generatorDiags);

var newCompDiags = newComp.GetDiagnostics();

// Verify the compilation failed with missing impl.
int missingImplCount = 0;
foreach (var diag in newCompDiags)
{
if ("CS8795".Equals(diag.Id))
{
missingImplCount++;
}
}

Assert.Equal(failCount, missingImplCount);
}
}
}
36 changes: 3 additions & 33 deletions DllImportGenerator/DllImportGenerator.Test/Compiles.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using Xunit;

namespace DllImportGenerator.Test
Expand All @@ -26,40 +22,14 @@ public static IEnumerable<object[]> CodeSnippetsToCompile()
[MemberData(nameof(CodeSnippetsToCompile))]
public void ValidateSnippets(string source)
{
Compilation comp = CreateCompilation(source);
var compDiags = comp.GetDiagnostics();
foreach (var diag in compDiags)
{
Assert.True(
"CS8795".Equals(diag.Id) // Partial method impl missing
|| "CS0234".Equals(diag.Id) // Missing type or namespace - GeneratedDllImportAttribute
|| "CS0246".Equals(diag.Id) // Missing type or namespace - GeneratedDllImportAttribute
|| "CS8019".Equals(diag.Id)); // Unnecessary using
}
Compilation comp = TestUtils.CreateCompilation(source);
TestUtils.AssertPreSourceGeneratorCompilation(comp);

var newComp = RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator());
var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator());
Assert.Empty(generatorDiags);

var newCompDiags = newComp.GetDiagnostics();
Assert.Empty(newCompDiags);
}

private static Compilation CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary)
=> CSharpCompilation.Create("compilation",
new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) },
new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
new CSharpCompilationOptions(outputKind));

private static GeneratorDriver CreateDriver(Compilation c, params ISourceGenerator[] generators)
=> new CSharpGeneratorDriver(c.SyntaxTrees.First().Options,
ImmutableArray.Create(generators),
null,
ImmutableArray<AdditionalText>.Empty);

private static Compilation RunGenerators(Compilation c, out ImmutableArray<Diagnostic> diagnostics, params ISourceGenerator[] generators)
{
CreateDriver(c, generators).RunFullGeneration(c, out var d, out diagnostics);
return d;
}
}
}
61 changes: 61 additions & 0 deletions DllImportGenerator/DllImportGenerator.Test/TestUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using Xunit;

namespace DllImportGenerator.Test
{
internal static class TestUtils
{
/// <summary>
/// Assert the pre-srouce generator compilation has only
/// the expected failure diagnostics.
/// </summary>
/// <param name="comp"></param>
public static void AssertPreSourceGeneratorCompilation(Compilation comp)
{
var compDiags = comp.GetDiagnostics();
foreach (var diag in compDiags)
{
Assert.True(
"CS8795".Equals(diag.Id) // Partial method impl missing
|| "CS0234".Equals(diag.Id) // Missing type or namespace - GeneratedDllImportAttribute
|| "CS0246".Equals(diag.Id) // Missing type or namespace - GeneratedDllImportAttribute
|| "CS8019".Equals(diag.Id)); // Unnecessary using
}
}

/// <summary>
/// Create a compilation given source
/// </summary>
/// <param name="source">Source to compile</param>
/// <param name="outputKind">Output type</param>
/// <returns>The resulting compilation</returns>
public static Compilation CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary)
=> CSharpCompilation.Create("compilation",
new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) },
new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
new CSharpCompilationOptions(outputKind));

/// <summary>
/// Run the supplied generators on the compilation.
/// </summary>
/// <param name="comp">Compilation target</param>
/// <param name="diagnostics">Resulting diagnostics</param>
/// <param name="generators">Source generator instances</param>
/// <returns>The resulting compilation</returns>
public static Compilation RunGenerators(Compilation comp, out ImmutableArray<Diagnostic> diagnostics, params ISourceGenerator[] generators)
{
CreateDriver(comp, generators).RunFullGeneration(comp, out var d, out diagnostics);
return d;
}

private static GeneratorDriver CreateDriver(Compilation c, params ISourceGenerator[] generators)
=> new CSharpGeneratorDriver(c.SyntaxTrees.First().Options,
ImmutableArray.Create(generators),
null,
ImmutableArray<AdditionalText>.Empty);
}
}
18 changes: 16 additions & 2 deletions DllImportGenerator/DllImportGenerator/DllImportGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,22 @@ public void Initialize(InitializationContext context)
private static bool IsGeneratedDllImportAttribute(AttributeSyntax attrSyntaxMaybe)
{
var attrName = attrSyntaxMaybe.Name.ToString();
return attrName.EndsWith(GeneratedDllImport)
|| attrName.EndsWith(GeneratedDllImportAttribute);

if (attrName.Length == GeneratedDllImport.Length)
{
return attrName.Equals(GeneratedDllImport);
}
else if (attrName.Length == GeneratedDllImportAttribute.Length)
{
return attrName.Equals(GeneratedDllImportAttribute);
}

// Handle the case where the user defines an attribute with
// the same name but adds a prefix.
const string PrefixedGeneratedDllImport = "." + GeneratedDllImport;
const string PrefixedGeneratedDllImportAttribute = "." + GeneratedDllImportAttribute;
return attrName.EndsWith(PrefixedGeneratedDllImport)
|| attrName.EndsWith(PrefixedGeneratedDllImportAttribute);
}

private IEnumerable<string> ProcessAttributes(
Expand Down
4 changes: 2 additions & 2 deletions DllImportGenerator/DllImportGenerator/DllImportStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ public static DllImportStub Create(IMethodSymbol method, CancellationToken token
// Forward call to generated P/Invoke
var returnMaybe = method.ReturnType.SpecialType == SpecialType.System_Void
? string.Empty
: "return";
: "return ";

var dispatchCall = new StringBuilder($"{returnMaybe} {dllImportName}");
var dispatchCall = new StringBuilder($"{returnMaybe}{dllImportName}");
if (!dllImportParams.Any())
{
dispatchCall.Append("();");
Expand Down

0 comments on commit d2f6f5a

Please sign in to comment.