提交 3679da94 编写于 作者: M Manish Vasani 提交者: GitHub

Add support for an OperationTreeVerifier - test only OperationWalker to dump...

Add support for an OperationTreeVerifier - test only OperationWalker to dump operation tree. (#11918)

The dump approach should enable to us to (relatively) easily extend the existing C# and VB binding tests to verify the operation tree.
I have moved the existing IOperation tests to the dump approach and also added couple of additional tests to demonstrate how to write these tests for entire method body operation tree verification.
上级 7e4f3149
......@@ -7,7 +7,6 @@
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
using Roslyn.Test.Utilities;
......
......@@ -12,7 +12,7 @@ public abstract class OperationWalker : OperationVisitor
{
private int _recursionDepth;
private void VisitArray<T>(ImmutableArray<T> list) where T : IOperation
internal void VisitArray<T>(ImmutableArray<T> list) where T : IOperation
{
if (!list.IsDefault)
{
......
......@@ -87,9 +87,11 @@ public static string ListToSortedString(this List<string> list)
return text;
}
// TODO: Remove this method and fix callsites to directly invoke Microsoft.CodeAnalysis.Test.Extensions.SymbolExtensions.ToTestDisplayString().
// https://github.com/dotnet/roslyn/issues/11915
public static string ToTestDisplayString(this ISymbol symbol)
{
return symbol.ToDisplayString(SymbolDisplayFormat.TestFormat);
return CodeAnalysis.Test.Extensions.SymbolExtensions.ToTestDisplayString(symbol);
}
}
}
......@@ -5,7 +5,6 @@
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
......@@ -87,9 +86,11 @@ public static string ListToSortedString(this List<string> list)
return text;
}
// TODO: Remove this method and fix callsites to directly invoke Microsoft.CodeAnalysis.Test.Extensions.SymbolExtensions.ToTestDisplayString().
// https://github.com/dotnet/roslyn/issues/11915
public static string ToTestDisplayString(this ISymbol symbol)
{
return symbol.ToDisplayString(SymbolDisplayFormat.TestFormat);
return CodeAnalysis.Test.Extensions.SymbolExtensions.ToTestDisplayString(symbol);
}
}
}
......@@ -3,10 +3,6 @@
Imports System.Collections.Immutable
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Xunit
Friend Module Extensions
......@@ -20,9 +16,11 @@ Friend Module Extensions
Return DirectCast(compilation.GetAssemblyOrModuleSymbol(reference), ModuleSymbol)
End Function
' TODO: Remove this method and fix callsites to directly invoke Microsoft.CodeAnalysis.Test.Extensions.SymbolExtensions.ToTestDisplayString().
' https://github.com/dotnet/roslyn/issues/11915
<Extension>
Public Function ToTestDisplayString(Symbol As ISymbol) As String
Return Symbol.ToDisplayString(SymbolDisplayFormat.TestFormat)
Public Function ToTestDisplayString(symbol As ISymbol) As String
Return Test.Extensions.SymbolExtensions.ToTestDisplayString(symbol)
End Function
Private Function SplitMemberName(qualifiedName As String) As ImmutableArray(Of String)
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Collections.Immutable
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.UnitTests.Diagnostics
Imports Microsoft.CodeAnalysis.UnitTests.Diagnostics.SystemLanguage
Imports Microsoft.CodeAnalysis.Semantics
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Roslyn.Test.Utilities
Imports Xunit
Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics
......@@ -77,6 +70,15 @@ End Module
Dim literal1 As ILiteralExpression = DirectCast(right1, ILiteralExpression)
Assert.Equal(CInt(literal1.ConstantValue.Value), 10)
comp.VerifyOperationTree(nodes(0), expectedOperationTree:="
IExpressionStatement (OperationKind.ExpressionStatement, IsInvalid)
IAssignmentExpression (OperationKind.AssignmentExpression, Type: B2, IsInvalid)
Left: ILocalReferenceExpression: x (OperationKind.LocalReferenceExpression, Type: B2)
Right: IBinaryOperatorExpression (BinaryOperationKind.OperatorMethodAdd) (OperationKind.BinaryOperatorExpression, Type: B2, IsInvalid)
Left: ILocalReferenceExpression: x (OperationKind.LocalReferenceExpression, Type: B2)
Right: ILiteralExpression (Text: 10) (OperationKind.LiteralExpression, Type: System.Int32, Constant: 10)
")
' x = x + y passes semantic analysis.
Assert.Equal("x = x + y", nodes(1).ToString())
......@@ -98,6 +100,15 @@ End Module
Assert.Equal(right2.Kind, OperationKind.LocalReferenceExpression)
Assert.Equal(DirectCast(right2, ILocalReferenceExpression).Local.Name, "y")
comp.VerifyOperationTree(nodes(1), expectedOperationTree:="
IExpressionStatement (OperationKind.ExpressionStatement)
IAssignmentExpression (OperationKind.AssignmentExpression, Type: B2)
Left: ILocalReferenceExpression: x (OperationKind.LocalReferenceExpression, Type: B2)
Right: IBinaryOperatorExpression (BinaryOperationKind.OperatorMethodAdd) (OperatorMethod: Function B2.op_Addition(x As B2, y As B2) As B2) (OperationKind.BinaryOperatorExpression, Type: B2)
Left: ILocalReferenceExpression: x (OperationKind.LocalReferenceExpression, Type: B2)
Right: ILocalReferenceExpression: y (OperationKind.LocalReferenceExpression, Type: B2)
")
' -x fails semantic analysis and does not have an operator method, but the operand is available.
Assert.Equal("x = -x", nodes(2).ToString())
......@@ -114,6 +125,14 @@ End Module
Dim operand3 As IOperation = negate3.Operand
Assert.Equal(operand3.Kind, OperationKind.LocalReferenceExpression)
Assert.Equal(DirectCast(operand3, ILocalReferenceExpression).Local.Name, "x")
comp.VerifyOperationTree(nodes(2), expectedOperationTree:="
IExpressionStatement (OperationKind.ExpressionStatement, IsInvalid)
IAssignmentExpression (OperationKind.AssignmentExpression, Type: B2, IsInvalid)
Left: ILocalReferenceExpression: x (OperationKind.LocalReferenceExpression, Type: B2)
Right: IUnaryOperatorExpression (UnaryOperationKind.OperatorMethodMinus) (OperationKind.UnaryOperatorExpression, Type: B2, IsInvalid)
ILocalReferenceExpression: x (OperationKind.LocalReferenceExpression, Type: B2)
")
End Sub
<Fact>
......@@ -164,6 +183,13 @@ End Module
Assert.False(assignment1.UsesOperatorMethod)
Assert.Null(assignment1.OperatorMethod)
comp.VerifyOperationTree(nodes(0), expectedOperationTree:="
IExpressionStatement (OperationKind.ExpressionStatement)
ICompoundAssignmentExpression (BinaryOperationKind.IntegerAdd) (OperationKind.CompoundAssignmentExpression, Type: System.Int32)
Left: ILocalReferenceExpression: x (OperationKind.LocalReferenceExpression, Type: System.Int32)
Right: ILocalReferenceExpression: y (OperationKind.LocalReferenceExpression, Type: System.Int32)
")
' a += b produces a compound assignment with an operator method add.
Assert.Equal("a += b", nodes(1).ToString())
......@@ -182,6 +208,85 @@ End Module
Assert.True(assignment2.UsesOperatorMethod)
Assert.NotNull(assignment2.OperatorMethod)
Assert.Equal(assignment2.OperatorMethod.Name, "op_Addition")
comp.VerifyOperationTree(nodes(1), expectedOperationTree:="
IExpressionStatement (OperationKind.ExpressionStatement)
ICompoundAssignmentExpression (BinaryOperationKind.OperatorMethodAdd) (OperatorMethod: Function B2.op_Addition(x As B2, y As B2) As B2) (OperationKind.CompoundAssignmentExpression, Type: B2)
Left: ILocalReferenceExpression: a (OperationKind.LocalReferenceExpression, Type: B2)
Right: ILocalReferenceExpression: b (OperationKind.LocalReferenceExpression, Type: B2)
")
End Sub
<Fact>
Public Sub VerifyOperationTree_IfStatement()
Dim source = <compilation>
<file name="c.vb">
<![CDATA[
Class C
Sub Foo(x as Integer)
If x <> 0
System.Console.Write(x)
End If
End Sub
End Class
]]>
</file>
</compilation>
CompilationUtils.CreateCompilationWithMscorlibAndVBRuntime(source) _
.VerifyOperationTree("Foo", "
Sub C.Foo(x As System.Int32)
IIfStatement (OperationKind.IfStatement)
Condition: IBinaryOperatorExpression (BinaryOperationKind.IntegerNotEquals) (OperationKind.BinaryOperatorExpression, Type: System.Boolean)
Left: IParameterReferenceExpression: x (OperationKind.ParameterReferenceExpression, Type: System.Int32)
Right: ILiteralExpression (Text: 0) (OperationKind.LiteralExpression, Type: System.Int32, Constant: 0)
IBlockStatement (1 statements) (OperationKind.BlockStatement)
IExpressionStatement (OperationKind.ExpressionStatement)
IInvocationExpression (static Sub System.Console.Write(value As System.Int32)) (OperationKind.InvocationExpression, Type: System.Void)
IArgument (Matching Parameter: value) (OperationKind.Argument)
IParameterReferenceExpression: x (OperationKind.ParameterReferenceExpression, Type: System.Int32)
")
End Sub
<Fact>
Public Sub VerifyOperationTree_ForStatement()
Dim source = <compilation>
<file name="c.vb">
<![CDATA[
Class C
Sub Foo()
For i = 0 To 10
System.Console.Write(i)
Next
End Sub
End Class
]]>
</file>
</compilation>
CompilationUtils.CreateCompilationWithMscorlibAndVBRuntime(source) _
.VerifyOperationTree("Foo", "
Sub C.Foo()
IForLoopStatement (LoopKind.For) (OperationKind.LoopStatement)
Condition: IBinaryOperatorExpression (BinaryOperationKind.IntegerLessThanOrEqual) (OperationKind.BinaryOperatorExpression, Type: System.Boolean)
Left: ILocalReferenceExpression: i (OperationKind.LocalReferenceExpression, Type: System.Int32)
Right: ILiteralExpression (Text: 10) (OperationKind.LiteralExpression, Type: System.Int32, Constant: 10)
Before: IExpressionStatement (OperationKind.ExpressionStatement)
IAssignmentExpression (OperationKind.AssignmentExpression, Type: System.Int32)
Left: ILocalReferenceExpression: i (OperationKind.LocalReferenceExpression, Type: System.Int32)
Right: ILiteralExpression (Text: 0) (OperationKind.LiteralExpression, Type: System.Int32, Constant: 0)
AtLoopBottom: IExpressionStatement (OperationKind.ExpressionStatement)
ICompoundAssignmentExpression (BinaryOperationKind.IntegerAdd) (OperationKind.CompoundAssignmentExpression, Type: System.Int32)
Left: ILocalReferenceExpression: i (OperationKind.LocalReferenceExpression, Type: System.Int32)
Right: IConversionExpression (ConversionKind.Basic, Explicit) (OperationKind.ConversionExpression, Type: System.Int32, Constant: 1)
ILiteralExpression (OperationKind.LiteralExpression, Type: System.Int32, Constant: 1)
IBlockStatement (1 statements) (OperationKind.BlockStatement)
IExpressionStatement (OperationKind.ExpressionStatement)
IInvocationExpression (static Sub System.Console.Write(value As System.Int32)) (OperationKind.InvocationExpression, Type: System.Void)
IArgument (Matching Parameter: value) (OperationKind.Argument)
ILocalReferenceExpression: i (OperationKind.LocalReferenceExpression, Type: System.Int32)
")
End Sub
End Class
End Namespace
......@@ -130,7 +130,7 @@ private string Emit(IRuntimeEnvironment testEnvironment, IEnumerable<ResourceDes
_diagnostics = testEnvironment.GetDiagnostics();
EmittedAssemblyData = testEnvironment.GetMainImage();
EmittedAssemblyPdb = testEnvironment.GetMainPdb();
_testData = ((IInternalRuntimeEnvironment) testEnvironment).GetCompilationTestData();
_testData = ((IInternalRuntimeEnvironment)testEnvironment).GetCompilationTestData();
return _compilation.Assembly.Identity.GetDisplayName();
}
......@@ -333,6 +333,16 @@ private IModuleSymbol GetModuleSymbolForEmittedImage(ImmutableArray<byte> peImag
return AssemblyMetadata.Create(moduleMetadata).GetReference(display: display);
}
}
public void VerifyOperationTree(string expectedOperationTree, bool skipImplicitlyDeclaredSymbols = false)
{
_compilation.VerifyOperationTree(expectedOperationTree, skipImplicitlyDeclaredSymbols);
}
public void VerifyOperationTree(string symbolToVerify, string expectedOperationTree, bool skipImplicitlyDeclaredSymbols = false)
{
_compilation.VerifyOperationTree(symbolToVerify, expectedOperationTree, skipImplicitlyDeclaredSymbols);
}
}
}
}
......@@ -5,13 +5,15 @@
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Extensions;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Sdk;
namespace Microsoft.CodeAnalysis.Test.Utilities
{
......@@ -148,5 +150,128 @@ internal static void VerifyAssemblyAliases(this Compilation compilation, params
AssertEx.Equal(expectedAssembliesAndAliases, actual, itemInspector: s => '"' + s + '"');
}
internal static void VerifyOperationTree(this Compilation compilation, SyntaxNode node, string expectedOperationTree)
{
var actualTextBuilder = new StringBuilder();
SemanticModel model = compilation.GetSemanticModel(node.SyntaxTree);
AppendOperationTree(model, node, actualTextBuilder);
VerifyOperationTree(expectedOperationTree, actualTextBuilder.ToString());
}
internal static void VerifyOperationTree(this Compilation compilation, string expectedOperationTree, bool skipImplicitlyDeclaredSymbols = false)
{
VerifyOperationTree(compilation, symbolToVerify: null, expectedOperationTree: expectedOperationTree, skipImplicitlyDeclaredSymbols: skipImplicitlyDeclaredSymbols);
}
internal static void VerifyOperationTree(this Compilation compilation, string symbolToVerify, string expectedOperationTree, bool skipImplicitlyDeclaredSymbols = false)
{
SyntaxTree tree = compilation.SyntaxTrees.First();
SyntaxNode root = tree.GetRoot();
SemanticModel model = compilation.GetSemanticModel(tree);
var declarations = new List<DeclarationInfo>();
model.ComputeDeclarationsInNode(root, getSymbol: true, builder: declarations, cancellationToken: CancellationToken.None);
var actualTextBuilder = new StringBuilder();
foreach (DeclarationInfo declaration in declarations.Where(d => d.DeclaredSymbol != null).OrderBy(d => d.DeclaredSymbol.ToTestDisplayString()))
{
if (!CanHaveExecutableCodeBlock(declaration.DeclaredSymbol))
{
continue;
}
if (skipImplicitlyDeclaredSymbols && declaration.DeclaredSymbol.IsImplicitlyDeclared)
{
continue;
}
if (!string.IsNullOrEmpty(symbolToVerify) && !declaration.DeclaredSymbol.Name.Equals(symbolToVerify, StringComparison.Ordinal))
{
continue;
}
actualTextBuilder.Append(declaration.DeclaredSymbol.ToTestDisplayString());
if (declaration.ExecutableCodeBlocks.Length == 0)
{
actualTextBuilder.Append($" ('0' executable code blocks)");
}
else
{
// Workaround for https://github.com/dotnet/roslyn/issues/11903 - skip the IOperation for EndBlockStatement.
ImmutableArray<SyntaxNode> executableCodeBlocks = declaration.ExecutableCodeBlocks;
if (declaration.DeclaredSymbol.Kind == SymbolKind.Method && compilation.Language == LanguageNames.VisualBasic)
{
executableCodeBlocks = executableCodeBlocks.RemoveAt(executableCodeBlocks.Length - 1);
}
foreach (SyntaxNode executableCodeBlock in executableCodeBlocks)
{
actualTextBuilder.Append(Environment.NewLine);
AppendOperationTree(model, executableCodeBlock, actualTextBuilder, initialIndent: 2);
}
}
actualTextBuilder.Append(Environment.NewLine);
}
VerifyOperationTree(expectedOperationTree, actualTextBuilder.ToString());
}
private static void AppendOperationTree(SemanticModel model, SyntaxNode node, StringBuilder actualTextBuilder, int initialIndent = 0)
{
IOperation operation = model.GetOperation(node);
if (operation != null)
{
string operationTree = OperationTreeVerifier.GetOperationTree(operation, initialIndent);
actualTextBuilder.Append(operationTree);
}
else
{
actualTextBuilder.Append($" SemanticModel.GetOperation() returned NULL for node with text: '{node.ToString()}'");
}
}
private static void VerifyOperationTree(string expectedOperationTree, string actualOperationTree)
{
var assertFailed = false;
char[] newLineChars = Environment.NewLine.ToCharArray();
string actual = actualOperationTree.Trim(newLineChars);
expectedOperationTree = expectedOperationTree.Trim(newLineChars);
try
{
Assert.Equal(expectedOperationTree, actual);
}
catch (EqualException)
{
assertFailed = true;
throw;
}
finally
{
if (assertFailed)
{
Console.WriteLine($"Actual operation tree:\r\n{actual}\r\n");
}
}
}
internal static bool CanHaveExecutableCodeBlock(ISymbol symbol)
{
switch (symbol.Kind)
{
case SymbolKind.Field:
case SymbolKind.Event:
case SymbolKind.Method:
case SymbolKind.NamedType:
case SymbolKind.Property:
return true;
default:
return false;
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.Test.Extensions
{
internal static class SymbolExtensions
{
public static string ToTestDisplayString(this ISymbol symbol)
{
return symbol.ToDisplayString(SymbolDisplayFormat.TestFormat);
}
}
}
......@@ -26,6 +26,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Compilation\CompilationOutputFiles.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Compilation\CompilationTestDataExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Compilation\IRuntimeEnvironment.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Compilation\OperationTreeVerifier.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Compilation\TestOperationWalker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Compilation\VersionTestHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\DescriptorFactory.cs" />
......@@ -37,6 +38,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\ThrowingDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\TrackingDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ExceptionHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SymbolExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FX\ConsoleOutput.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FX\CultureHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FX\DirectoryHelper.cs" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册