Skip to content

Commit

Permalink
Use Roslyn Source Generator Testing SDK to test interop source genera…
Browse files Browse the repository at this point in the history
…tors (#84867)
  • Loading branch information
jkoritzinsky authored May 8, 2023
1 parent 4fb1b76 commit fc1cb19
Show file tree
Hide file tree
Showing 39 changed files with 2,215 additions and 1,247 deletions.
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@
<GrpcToolsVersion>2.45.0</GrpcToolsVersion>
<!-- Uncomment to set a fixed version, else the latest is used -->
<!-- <SdkVersionForWorkloadTesting>8.0.100-alpha.1.23077.3</SdkVersionForWorkloadTesting> -->
<CompilerPlatformTestingVersion>1.1.2-beta1.22403.2</CompilerPlatformTestingVersion>
<CompilerPlatformTestingVersion>1.1.2-beta1.23205.1</CompilerPlatformTestingVersion>
<!-- Docs -->
<MicrosoftPrivateIntellisenseVersion>7.0.0-preview-20221010.1</MicrosoftPrivateIntellisenseVersion>
<!-- ILLink -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ private static IncrementalMethodStubGenerationContext CalculateStubInformation(M
{
if (baseInterface is not null)
{
return Diagnostic.Create(GeneratorDiagnostics.MultipleComInterfaceBaseTypesAttribute, syntax.Identifier.GetLocation(), type.ToDisplayString());
return Diagnostic.Create(GeneratorDiagnostics.MultipleComInterfaceBaseTypes, syntax.Identifier.GetLocation(), type.ToDisplayString());
}
baseInterface = implemented;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public class Ids
isEnabledByDefault: true,
description: GetResourceString(nameof(SR.InvalidGeneratedComInterfaceAttributeUsageDescription)));

public static readonly DiagnosticDescriptor MultipleComInterfaceBaseTypesAttribute =
public static readonly DiagnosticDescriptor MultipleComInterfaceBaseTypes =
new DiagnosticDescriptor(
Ids.MultipleComInterfaceBaseTypes,
GetResourceString(nameof(SR.MultipleComInterfaceBaseTypesTitle)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ private ParenthesizedExpressionSyntax CreateFunctionPointerExpression(
{
List<FunctionPointerParameterSyntax> functionPointerParameters = new();
var (paramList, retType, _) = _marshallers.GenerateTargetMethodSignatureData(_context);
functionPointerParameters.AddRange(paramList.Parameters.Select(p => FunctionPointerParameter(p.Type)));
functionPointerParameters.AddRange(paramList.Parameters.Select(p => FunctionPointerParameter(attributeLists: default, p.Modifiers, p.Type)));
functionPointerParameters.Add(FunctionPointerParameter(retType));

// ((delegate* unmanaged<...>)<untypedFunctionPointerExpression>)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.Interop.UnitTests;
using Microsoft.CodeAnalysis.Testing;
using Xunit;

using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier<Microsoft.Interop.VtableIndexStubGenerator>;

namespace ComInterfaceGenerator.Unit.Tests
{
public class CallingConventionForwarding
Expand All @@ -31,16 +31,12 @@ partial interface INativeAPI : IUnmanagedInterfaceType
void Method();
}
""";
Compilation comp = await TestUtils.CreateCompilation(source);
// Allow the Native nested type name to be missing in the pre-source-generator compilation
TestUtils.AssertPreSourceGeneratorCompilation(comp);

var newComp = TestUtils.RunGenerators(comp, out _, new Microsoft.Interop.VtableIndexStubGenerator());

var signature = await FindFunctionPointerInvocationSignature(newComp, "INativeAPI", "Method");

Assert.Equal(SignatureCallingConvention.Unmanaged, signature.CallingConvention);
Assert.Empty(signature.UnmanagedCallingConventionTypes);
await VerifySourceGeneratorAsync(source, "INativeAPI", "Method", (compilation, signature) =>
{
Assert.Equal(SignatureCallingConvention.Unmanaged, signature.CallingConvention);
Assert.Empty(signature.UnmanagedCallingConventionTypes);
});
}

[Fact]
Expand All @@ -59,16 +55,12 @@ partial interface INativeAPI : IUnmanagedInterfaceType
void Method();
}
""";
Compilation comp = await TestUtils.CreateCompilation(source);
// Allow the Native nested type name to be missing in the pre-source-generator compilation
TestUtils.AssertPreSourceGeneratorCompilation(comp);

var newComp = TestUtils.RunGenerators(comp, out _, new Microsoft.Interop.VtableIndexStubGenerator());

var signature = await FindFunctionPointerInvocationSignature(newComp, "INativeAPI", "Method");

Assert.Equal(SignatureCallingConvention.Unmanaged, signature.CallingConvention);
Assert.Equal(newComp.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvSuppressGCTransition"), Assert.Single(signature.UnmanagedCallingConventionTypes), SymbolEqualityComparer.Default);
await VerifySourceGeneratorAsync(source, "INativeAPI", "Method", (newComp, signature) =>
{
Assert.Equal(SignatureCallingConvention.Unmanaged, signature.CallingConvention);
Assert.Equal(newComp.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvSuppressGCTransition"), Assert.Single(signature.UnmanagedCallingConventionTypes), SymbolEqualityComparer.Default);
});
}

[Fact]
Expand All @@ -87,22 +79,19 @@ partial interface INativeAPI : IUnmanagedInterfaceType
void Method();
}
""";
Compilation comp = await TestUtils.CreateCompilation(source);
// Allow the Native nested type name to be missing in the pre-source-generator compilation
TestUtils.AssertPreSourceGeneratorCompilation(comp);

var newComp = TestUtils.RunGenerators(comp, out _, new Microsoft.Interop.VtableIndexStubGenerator());

var signature = await FindFunctionPointerInvocationSignature(newComp, "INativeAPI", "Method");

Assert.Equal(SignatureCallingConvention.Unmanaged, signature.CallingConvention);
Assert.Empty(signature.UnmanagedCallingConventionTypes);
await VerifySourceGeneratorAsync(source, "INativeAPI", "Method", (_, signature) =>
{
Assert.Equal(SignatureCallingConvention.Unmanaged, signature.CallingConvention);
Assert.Empty(signature.UnmanagedCallingConventionTypes);
});
}

[Fact]
public async Task SimpleUnmanagedCallConvAttributeForwarded()
{
string source = $$"""
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

Expand All @@ -115,22 +104,19 @@ partial interface INativeAPI : IUnmanagedInterfaceType
void Method();
}
""";
Compilation comp = await TestUtils.CreateCompilation(source);
// Allow the Native nested type name to be missing in the pre-source-generator compilation
TestUtils.AssertPreSourceGeneratorCompilation(comp);

var newComp = TestUtils.RunGenerators(comp, out _, new Microsoft.Interop.VtableIndexStubGenerator());

var signature = await FindFunctionPointerInvocationSignature(newComp, "INativeAPI", "Method");

Assert.Equal(SignatureCallingConvention.CDecl, signature.CallingConvention);
Assert.Empty(signature.UnmanagedCallingConventionTypes);
await VerifySourceGeneratorAsync(source, "INativeAPI", "Method", (_, signature) =>
{
Assert.Equal(SignatureCallingConvention.CDecl, signature.CallingConvention);
Assert.Empty(signature.UnmanagedCallingConventionTypes);
});
}

[Fact]
public async Task ComplexUnmanagedCallConvAttributeForwarded()
{
string source = $$"""
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

Expand All @@ -143,28 +129,25 @@ partial interface INativeAPI : IUnmanagedInterfaceType
void Method();
}
""";
Compilation comp = await TestUtils.CreateCompilation(source);
// Allow the Native nested type name to be missing in the pre-source-generator compilation
TestUtils.AssertPreSourceGeneratorCompilation(comp);

var newComp = TestUtils.RunGenerators(comp, out _, new Microsoft.Interop.VtableIndexStubGenerator());

var signature = await FindFunctionPointerInvocationSignature(newComp, "INativeAPI", "Method");

Assert.Equal(SignatureCallingConvention.Unmanaged, signature.CallingConvention);
Assert.Equal(new[]
await VerifySourceGeneratorAsync(source, "INativeAPI", "Method", (newComp, signature) =>
{
newComp.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvCdecl"),
newComp.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvMemberFunction"),
},
signature.UnmanagedCallingConventionTypes,
SymbolEqualityComparer.Default);
Assert.Equal(SignatureCallingConvention.Unmanaged, signature.CallingConvention);
Assert.Equal(new[]
{
newComp.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvCdecl"),
newComp.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvMemberFunction"),
},
signature.UnmanagedCallingConventionTypes,
SymbolEqualityComparer.Default);
});
}

[Fact]
public async Task ComplexUnmanagedCallConvAttributeWithSuppressGCTransitionForwarded()
{
string source = $$"""
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

Expand All @@ -178,41 +161,67 @@ partial interface INativeAPI : IUnmanagedInterfaceType
void Method();
}
""";
Compilation comp = await TestUtils.CreateCompilation(source);
// Allow the Native nested type name to be missing in the pre-source-generator compilation
TestUtils.AssertPreSourceGeneratorCompilation(comp);

var newComp = TestUtils.RunGenerators(comp, out _, new Microsoft.Interop.VtableIndexStubGenerator());

var signature = await FindFunctionPointerInvocationSignature(newComp, "INativeAPI", "Method");
await VerifySourceGeneratorAsync(source, "INativeAPI", "Method", (newComp, signature) =>
{
Assert.Equal(SignatureCallingConvention.Unmanaged, signature.CallingConvention);
Assert.Equal(new[]
{
newComp.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvSuppressGCTransition"),
newComp.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvCdecl"),
newComp.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvMemberFunction"),
},
signature.UnmanagedCallingConventionTypes,
SymbolEqualityComparer.Default);
});
}

Assert.Equal(SignatureCallingConvention.Unmanaged, signature.CallingConvention);
Assert.Equal(new[]
private static async Task VerifySourceGeneratorAsync(string source, string interfaceName, string methodName, Action<Compilation, IMethodSymbol> signatureValidator)
{
CallingConventionForwardingTest test = new(interfaceName, methodName, signatureValidator)
{
newComp.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvSuppressGCTransition"),
newComp.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvCdecl"),
newComp.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvMemberFunction"),
},
signature.UnmanagedCallingConventionTypes,
SymbolEqualityComparer.Default);
TestCode = source,
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
};

await test.RunAsync();
}

private static async Task<IMethodSymbol> FindFunctionPointerInvocationSignature(Compilation compilation, string userDefinedInterfaceName, string methodName)
class CallingConventionForwardingTest : VerifyCS.Test
{
INamedTypeSymbol? userDefinedInterface = compilation.Assembly.GetTypeByMetadataName(userDefinedInterfaceName);
Assert.NotNull(userDefinedInterface);
private readonly Action<Compilation, IMethodSymbol> _signatureValidator;
private readonly string _interfaceName;
private readonly string _methodName;

public CallingConventionForwardingTest(string interfaceName, string methodName, Action<Compilation, IMethodSymbol> signatureValidator)
: base(referenceAncillaryInterop: true)
{
_signatureValidator = signatureValidator;
_interfaceName = interfaceName;
_methodName = methodName;
}

protected override void VerifyFinalCompilation(Compilation compilation)
{
_signatureValidator(compilation, FindFunctionPointerInvocationSignature(compilation));
}
private IMethodSymbol FindFunctionPointerInvocationSignature(Compilation compilation)
{
INamedTypeSymbol? userDefinedInterface = compilation.Assembly.GetTypeByMetadataName(_interfaceName);
Assert.NotNull(userDefinedInterface);

INamedTypeSymbol generatedInterfaceImplementation = Assert.Single(userDefinedInterface.GetTypeMembers("Native"));
INamedTypeSymbol generatedInterfaceImplementation = Assert.Single(userDefinedInterface.GetTypeMembers("Native"));

IMethodSymbol methodImplementation = Assert.Single(generatedInterfaceImplementation.GetMembers($"global::{userDefinedInterfaceName}.{methodName}").OfType<IMethodSymbol>());
IMethodSymbol methodImplementation = Assert.Single(generatedInterfaceImplementation.GetMembers($"global::{_interfaceName}.{_methodName}").OfType<IMethodSymbol>());

SyntaxNode emittedImplementationSyntax = await methodImplementation.DeclaringSyntaxReferences[0].GetSyntaxAsync();
SyntaxNode emittedImplementationSyntax = methodImplementation.DeclaringSyntaxReferences[0].GetSyntax();

SemanticModel model = compilation.GetSemanticModel(emittedImplementationSyntax.SyntaxTree);
SemanticModel model = compilation.GetSemanticModel(emittedImplementationSyntax.SyntaxTree);

IOperation body = model.GetOperation(emittedImplementationSyntax)!;
IOperation body = model.GetOperation(emittedImplementationSyntax)!;

return Assert.Single(body.Descendants().OfType<IFunctionPointerInvocationOperation>()).GetFunctionPointerSignature();
return Assert.Single(body.Descendants().OfType<IFunctionPointerInvocationOperation>()).GetFunctionPointerSignature();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public string BasicParametersAndModifiers(string typeName, string methodModifier
partial interface INativeAPI
{
{{VirtualMethodIndex(0)}}
{{methodModifiers}} {{typeName}} Method({{typeName}} value, in {{typeName}} inValue, ref {{typeName}} refValue, out {{typeName}} outValue);
{{methodModifiers}} {{typeName}} {|#0:Method|}({{typeName}} {|#1:value|}, in {{typeName}} {|#2:inValue|}, ref {{typeName}} {|#3:refValue|}, out {{typeName}} {|#4:outValue|});
}
{{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}}
""";
Expand Down Expand Up @@ -277,7 +277,7 @@ partial interface IOtherComInterface
void MethodA();
}
{{GeneratedComInterface}}
partial interface IComInterface2 : IComInterface, IOtherComInterface
partial interface {|#0:IComInterface2|} : IComInterface, IOtherComInterface
{
void Method2();
}
Expand Down
Loading

0 comments on commit fc1cb19

Please sign in to comment.