-
Notifications
You must be signed in to change notification settings - Fork 128
[DAM analyzer] Check for unexpected analyzer warnings in linker tests #2523
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
89db6a1
Add test run to check for unexpected analyzer warnings
sbomer 412bc00
Allow more flexible test file organization
sbomer 06aeeba
Check expected warning locations
sbomer d727951
Respect ExpectedNoWarningsAttribute
sbomer f723d31
Fix build
sbomer a0d79f5
Fix inconsistencies in Requires attribute checks (#2553)
sbomer 574dd2b
Write generated code to disk
sbomer 9edcc03
Merge remote-tracking branch 'origin/feature/damAnalyzer' into noExtr…
sbomer 93c3f7e
Merge remote-tracking branch 'origin/feature/damAnalyzer' into noExtr…
sbomer 274e9da
Add generated sources
sbomer 50021c6
Add job displayName
sbomer 61b8d75
Try different property syntax
sbomer 1400962
Try removing comment syntax
sbomer 444257c
Don't produce warnings for typeof(Foo<>)
sbomer f662785
Don't crash on InvalidOperation
sbomer f31842e
Compare symbols correctly
sbomer e3b2cb4
Don't run analyzer on .il test dependencies
sbomer cc5ab84
Mak LinkAttributesTests partial
sbomer de48f49
Merge remote-tracking branch 'origin/feature/damAnalyzer' into noExtr…
sbomer 2a0cbf9
Fix test infra bugs with LogContains attributes
sbomer 73218c8
Skip failing tests
sbomer 094a43e
Fix remaining test infrastructure issues
sbomer 8946e50
Run generator for all analyzer test runs
sbomer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
test/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.Generator.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisVersion)" /> | ||
</ItemGroup> | ||
|
||
</Project> |
208 changes: 208 additions & 0 deletions
208
test/ILLink.RoslynAnalyzer.Tests.Generator/TestCaseGenerator.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using System.IO; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Reflection.Metadata; | ||
|
||
namespace ILLink.RoslynAnalyzer.Tests | ||
{ | ||
public readonly struct TestCases | ||
{ | ||
// Maps from suite name to a set of testcase names. | ||
// Suite name is: | ||
// - The namespace of the test class, minus "Mono.Linker.Tests.Cases", for linker tests | ||
// - The namespace + test class name, minus "ILLink.RoslynAnalyzer.Tests", for analyzer tests | ||
// Testcase name is: | ||
// - The test class name, for linker tests | ||
// - The test fact method name, minus "Tests", for analyzer tests | ||
// For example: | ||
// | Testcase | Suite name | Linker | Analyzer | | ||
// |-------------------------+----------------------+--------------------------------------------+-----------------------------------------| | ||
// | RequiresCapability | RequiresCapability | RequiresCapability class in namespcae | RequiresCapability method in class | | ||
// | | | Mono.Linker.Tests.Cases.RequiresCapability | RequiresCapabilityTests in namespace | | ||
// | | | | ILLink.RoslynAnalyzer.Tests | | ||
// |-------------------------+----------------------+--------------------------------------------+-----------------------------------------| | ||
// | RequiresOnAttributeCtor | RequiresCapability | RequiresOnAttributeCtor class in namespace | RequiresOnAttributeCtor method in class | | ||
// | | | Mono.Linker.Tests.Cases.RequiresCapability | RequiresCapabilityTests in namespace | | ||
// | | | | ILlink.RoslynAnalyzer.Tests | | ||
// |-------------------------+----------------------+--------------------------------------------+-----------------------------------------| | ||
// | UnusedPInvoke | Interop.PInvokeTests | UnusedPInvoke class in namespace | UnusedPInvoke method in class | | ||
// | | | Mono.Linker.Tests.Cases.Interop.PInvoke | PinvokeTests in namespace | | ||
// | | | | ILLink.RoslynAnalyzer.Tests.Interop | | ||
public readonly Dictionary<string, HashSet<string>> Suites = new (); | ||
|
||
public void Add (string suiteName, string name) | ||
{ | ||
if (!Suites.TryGetValue (suiteName, out var testCases)) { | ||
testCases = new HashSet<string> (); | ||
Suites.Add (suiteName, testCases); | ||
} | ||
|
||
testCases.Add (name); | ||
} | ||
} | ||
|
||
public static class TestClassGenerator | ||
{ | ||
public const string TestNamespace = "ILLink.RoslynAnalyzer.Tests"; | ||
|
||
public static string GenerateClassHeader (string suiteName, bool newTestSuite) { | ||
int idx = suiteName.LastIndexOf ('.'); | ||
// Test suite class from innermost namespace part | ||
var suiteClassName = suiteName.Substring (idx + 1); | ||
// Namespace from outer namespaces, or empty | ||
var suiteNamespacePart = suiteClassName.Length < suiteName.Length ? | ||
$".{suiteName.Substring (0, idx)}" : string.Empty; | ||
|
||
string header = $@"using System; | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
|
||
namespace {TestNamespace}{suiteNamespacePart} | ||
{{ | ||
public sealed partial class {suiteClassName}Tests : LinkerTestBase | ||
{{ | ||
"; | ||
if (newTestSuite) | ||
header += $@" | ||
protected override string TestSuiteName => ""{suiteName}""; | ||
"; | ||
return header; | ||
} | ||
|
||
public static string GenerateFact (string testCase) { | ||
return $@" | ||
[Fact] | ||
public Task {testCase} () | ||
{{ | ||
return RunTest (allowMissingWarnings: true); | ||
}} | ||
"; | ||
} | ||
|
||
public static string GenerateClassFooter () { | ||
return $@" | ||
}} | ||
}}"; | ||
} | ||
} | ||
|
||
[Generator] | ||
public class TestCaseGenerator : ISourceGenerator | ||
{ | ||
public const string TestCaseAssembly = "Mono.Linker.Tests.Cases"; | ||
|
||
public void Execute (GeneratorExecutionContext context) | ||
{ | ||
IAssemblySymbol? assemblySymbol = null; | ||
|
||
// Find testcase assembly | ||
foreach (var reference in context.Compilation.References) { | ||
ISymbol? assemblyOrModule = context.Compilation.GetAssemblyOrModuleSymbol (reference); | ||
if (assemblyOrModule is IAssemblySymbol asmSym && asmSym.Name == TestCaseAssembly) { | ||
assemblySymbol = asmSym; | ||
break; | ||
} | ||
} | ||
if (assemblySymbol is null || assemblySymbol.GetMetadata () is not AssemblyMetadata assemblyMetadata) | ||
return; | ||
|
||
ModuleMetadata moduleMetadata = assemblyMetadata.GetModules ().Single (); | ||
MetadataReader metadataReader = moduleMetadata.GetMetadataReader (); | ||
TestCases testCases = new (); | ||
string suiteName; | ||
|
||
// Find test classes | ||
foreach (var typeDefHandle in metadataReader.TypeDefinitions) { | ||
TypeDefinition typeDef = metadataReader.GetTypeDefinition (typeDefHandle); | ||
// Must not be a nested type | ||
if (typeDef.IsNested) | ||
continue; | ||
|
||
string ns = metadataReader.GetString (typeDef.Namespace); | ||
// Must be in the testcases namespace | ||
if (!ns.StartsWith (TestCaseAssembly)) | ||
continue; | ||
|
||
// Must have a Main method | ||
bool hasMain = false; | ||
foreach (var methodDefHandle in typeDef.GetMethods ()) { | ||
MethodDefinition methodDef = metadataReader.GetMethodDefinition (methodDefHandle); | ||
if (metadataReader.GetString (methodDef.Name) == "Main") { | ||
hasMain = true; | ||
break; | ||
} | ||
} | ||
if (!hasMain) | ||
continue; | ||
|
||
string testName = metadataReader.GetString (typeDef.Name); | ||
suiteName = ns.Substring (TestCaseAssembly.Length + 1); | ||
|
||
testCases.Add (suiteName, testName); | ||
} | ||
|
||
TestCases existingTestCases = ((ExistingTestCaseDiscoverer) context.SyntaxContextReceiver!).ExistingTestCases; | ||
|
||
// Generate test facts | ||
foreach (var kvp in testCases.Suites) { | ||
suiteName = kvp.Key; | ||
var cases = kvp.Value; | ||
|
||
bool newTestSuite = !existingTestCases.Suites.TryGetValue (suiteName, out HashSet<string> existingCases); | ||
var newCases = newTestSuite ? cases : cases.Except (existingCases); | ||
// Skip generating a test class if all testcases in the suite already exist. | ||
if (!newCases.Any ()) | ||
continue; | ||
|
||
StringBuilder sourceBuilder = new (); | ||
sourceBuilder.Append (TestClassGenerator.GenerateClassHeader (suiteName, newTestSuite)); | ||
// Generate facts for all testcases that don't already exist | ||
foreach (var testCase in newCases) | ||
sourceBuilder.Append (TestClassGenerator.GenerateFact (testCase)); | ||
sourceBuilder.Append (TestClassGenerator.GenerateClassFooter ()); | ||
|
||
context.AddSource ($"{suiteName}Tests.g.cs", sourceBuilder.ToString ()); | ||
} | ||
} | ||
|
||
public void Initialize (GeneratorInitializationContext context) | ||
{ | ||
context.RegisterForSyntaxNotifications (() => new ExistingTestCaseDiscoverer ()); | ||
} | ||
} | ||
|
||
class ExistingTestCaseDiscoverer : ISyntaxContextReceiver | ||
{ | ||
public readonly TestCases ExistingTestCases = new (); | ||
|
||
public void OnVisitSyntaxNode (GeneratorSyntaxContext context) | ||
{ | ||
if (context.Node is not ClassDeclarationSyntax classSyntax) | ||
return; | ||
|
||
if (context.SemanticModel.GetDeclaredSymbol (classSyntax) is not INamedTypeSymbol typeSymbol) | ||
return; | ||
|
||
var displayFormat = new SymbolDisplayFormat( | ||
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces); | ||
string typeFullName = typeSymbol.ToDisplayString (displayFormat); | ||
|
||
// Ignore types not in the testcase namespace or that don't end with "Tests" | ||
if (!typeFullName.StartsWith (TestClassGenerator.TestNamespace)) | ||
return; | ||
if (!typeFullName.EndsWith ("Tests")) | ||
return; | ||
|
||
string suiteName = typeFullName.Substring (TestClassGenerator.TestNamespace.Length + 1); | ||
suiteName = suiteName.Substring (0, suiteName.Length - 5); | ||
foreach (var memberSymbol in typeSymbol.GetMembers ()) { | ||
if (memberSymbol is not IMethodSymbol methodSymbol) | ||
continue; | ||
ExistingTestCases.Add (suiteName, methodSymbol.Name); | ||
} | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
|
||
namespace ILLink.RoslynAnalyzer.Tests | ||
{ | ||
public sealed partial class AdvancedTests : LinkerTestBase | ||
{ | ||
protected override string TestSuiteName => "Advanced"; | ||
|
||
[Fact (Skip = "https://github.com/dotnet/linker/issues/2415")] | ||
public Task TypeCheckRemoval () | ||
{ | ||
return RunTest (allowMissingWarnings: true); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 8 additions & 1 deletion
9
test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,24 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<Nullable>enable</Nullable> | ||
<DeterministicSourcePaths>false</DeterministicSourcePaths> | ||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> | ||
<CompilerGeneratedFilesOutputPath>generated</CompilerGeneratedFilesOutputPath> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Compile Remove="$(CompilerGeneratedFilesOutputPath)" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="$(MicrosoftCodeAnalysisCSharpAnalyzerTestingXunitVersion)" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="$(MicrosoftCodeAnalysisCSharpAnalyzerTestingXunitVersion)" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion)" /> | ||
<ProjectReference Include="..\..\src\ILLink.CodeFix\ILLink.CodeFixProvider.csproj" /> | ||
<ProjectReference Include="..\..\src\ILLink.RoslynAnalyzer\ILLink.RoslynAnalyzer.csproj" /> | ||
<ProjectReference Include="../Mono.Linker.Tests.Cases\Mono.Linker.Tests.Cases.csproj" /> | ||
<ProjectReference Include="../ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really understand this - we ask the compiler to write the generated code to disk (I get that, at least for validation purposes and such). But then remove it from the compilation... this is probably because the source generator already added it?
Also - why do we commit the generated files into the source control, if they are always regenerated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, if we didn't remove them from the compilation they would be defined twice. I'm committing them to source control to make it easier to review changes, and because it sounds like the test UI may have trouble displaying generated tests otherwise. I don't have a strong opinion on it but this is per @agocke's suggestion - happy to remove them if we would rather not check in a bunch of generated sources.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For code that you're expected to interact w/ directly (e.g. tests), I'd prefer to have them checked in. VS Code can't run them individually otherwise
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, to properly enable these test cases, it will be easy to move the file into a non-generated directory. The generator will see that the test file is already present and will stop generating it, and git will understand and track the move operation.