Skip to content

Commit

Permalink
add analyzer reflection tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jmarolf committed Jun 24, 2020
1 parent 0a5c927 commit adf693c
Show file tree
Hide file tree
Showing 7 changed files with 343 additions and 6 deletions.
6 changes: 2 additions & 4 deletions src/Analyzers/AnalyzerFinderHelpers.cs
Expand Up @@ -6,17 +6,15 @@
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools.Analyzers
{
internal static class AnalyzerFinderHelpers
{
public static ImmutableArray<(DiagnosticAnalyzer Analyzer, CodeFixProvider? Fixer)> LoadAnalyzersAndFixers(
IEnumerable<Assembly> assemblies,
ILogger logger)
public static ImmutableArray<(DiagnosticAnalyzer Analyzer, CodeFixProvider? Fixer)> LoadAnalyzersAndFixers(IEnumerable<Assembly> assemblies)
{
var types = assemblies
.SelectMany(assembly => assembly.GetTypes()
Expand Down
2 changes: 1 addition & 1 deletion src/Analyzers/AnalyzerReferenceAnalyzerFinder.cs
Expand Up @@ -30,7 +30,7 @@ internal class AnalyzerReferenceAnalyzerFinder : IAnalyzerFinder
.Distinct()
.Select(path => Assembly.LoadFrom(path));

return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies, logger);
return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
}

public Task<ImmutableDictionary<Project, ImmutableArray<DiagnosticAnalyzer>>> FilterBySeverityAsync(
Expand Down
2 changes: 1 addition & 1 deletion src/Analyzers/RoslynCodeStyleAnalyzerFinder.cs
Expand Up @@ -36,7 +36,7 @@ internal class RoslynCodeStyleAnalyzerFinder : IAnalyzerFinder
_featuresVisualBasicPath
}.Select(path => Assembly.LoadFrom(path));

return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies, logger);
return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
}

public Task<ImmutableDictionary<Project, ImmutableArray<DiagnosticAnalyzer>>> FilterBySeverityAsync(
Expand Down
112 changes: 112 additions & 0 deletions tests/Analyzers/AnalyzerAssemblyGenerator.cs
@@ -0,0 +1,112 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;

namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers
{
public static class AnalyzerAssemblyGenerator
{
public static SyntaxTree GenerateCodeFix(string typeName, string diagnosticId)
{
var codefix = $@"
using System;
using System.Collections.Immutable;
using System.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof({typeName})), Shared]
public class {typeName} : CodeFixProvider
{{
public const string DiagnosticId = ""{diagnosticId}"";
public sealed override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(DiagnosticId);
public sealed override FixAllProvider GetFixAllProvider()
{{
return WellKnownFixAllProviders.BatchFixer;
}}
public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
{{
throw new NotImplementedException();
}}
}}";
return CSharpSyntaxTree.ParseText(codefix);
}

public static SyntaxTree GenerateAnalyzerCode(string typeName, string diagnosticId)
{
var analyzer = $@"
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class {typeName} : DiagnosticAnalyzer
{{
public const string DiagnosticId = ""{diagnosticId}"";
internal static readonly LocalizableString Title = ""{typeName} Title"";
internal static readonly LocalizableString MessageFormat = ""{typeName} '{{0}}'"";
internal const string Category = ""{typeName} Category"";
internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{{
}}
}}";
return CSharpSyntaxTree.ParseText(analyzer);
}

public static async Task<Assembly> GenerateAssemblyAsync(params SyntaxTree[] trees)
{
var assemblyName = Guid.NewGuid().ToString();
var references = new List<MetadataReference>()
{
MetadataReference.CreateFromFile(typeof(ImmutableArray).Assembly.Location),
MetadataReference.CreateFromFile(typeof(SharedAttribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location),
MetadataReference.CreateFromFile(typeof(DiagnosticAnalyzer).Assembly.Location),
MetadataReference.CreateFromFile(typeof(CodeFixProvider).Assembly.Location),
};

var netstandardMetaDataReferences = await ReferenceAssemblies.NetStandard.NetStandard20.ResolveAsync(LanguageNames.CSharp, CancellationToken.None);
references.AddRange(netstandardMetaDataReferences);
var compilation = CSharpCompilation.Create(assemblyName, trees, references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
var result = compilation.Emit(ms);
if (!result.Success)
{
var failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error)
.Select(diagnostic => $"{diagnostic.Id}: {diagnostic.GetMessage()}");

throw new Exception(string.Join(Environment.NewLine, failures));
}
else
{
ms.Seek(0, SeekOrigin.Begin);
var assembly = Assembly.Load(ms.ToArray());
return assembly;
}
}
}
}
}
83 changes: 83 additions & 0 deletions tests/Analyzers/FilterDiagnosticsTests.cs
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Tools.Analyzers;
using Microsoft.CodeAnalysis.Tools.Formatters;
using Microsoft.CodeAnalysis.Tools.Tests.Formatters;

using Xunit;

namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers
{
using static AnalyzerAssemblyGenerator;

public class FilterDiagnosticsTests : CSharpFormatterTests
{
[Fact]
public async Task TestFilterWarning()
{
var projects = GetProjects();
var allAnalyzers = await GetAnalyzersAsync();
var formattablePaths = ImmutableHashSet.Create(projects.First().Documents.First().FilePath);
var minimumSeverity = DiagnosticSeverity.Warning;
var result = await AnalyzerFinderHelpers.FilterBySeverityAsync(projects,
allAnalyzers,
formattablePaths,
minimumSeverity,
CancellationToken.None);
var (_, analyzers) = Assert.Single(result);
Assert.Single(analyzers);
}

[Fact]
public async Task TestFilterError()
{
var projects = GetProjects();
var allAnalyzers = await GetAnalyzersAsync();
var formattablePaths = ImmutableHashSet.Create(projects.First().Documents.First().FilePath);
var minimumSeverity = DiagnosticSeverity.Error;
var result = await AnalyzerFinderHelpers.FilterBySeverityAsync(projects,
allAnalyzers,
formattablePaths,
minimumSeverity,
CancellationToken.None);
var (_, analyzers) = Assert.Single(result);
Assert.Empty(analyzers);
}

private async Task<ImmutableArray<DiagnosticAnalyzer>> GetAnalyzersAsync()
{
var assemblies = new[]
{
await GenerateAssemblyAsync(
GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId"),
GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId"))
};

var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
return ImmutableArray.Create(analyzersAndFixers[0].Analyzer);
}

private IEnumerable<Project> GetProjects()
{
var text = SourceText.From("");
TestState.Sources.Add(text);

var solution = GetSolution(TestState.Sources.ToArray(),
TestState.AdditionalFiles.ToArray(),
TestState.AdditionalReferences.ToArray(),
"root = true");
return solution.Projects;
}

private protected override ICodeFormatter Formatter { get; }
}
}
141 changes: 141 additions & 0 deletions tests/Analyzers/LoadAnalyzersAndFixersTests.cs
@@ -0,0 +1,141 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;

using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Tools.Analyzers;

using Xunit;

namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers
{
using static AnalyzerAssemblyGenerator;

public class LoadAnalyzersAndFixersTests
{
[Fact]
public static async Task TestSingleAnalyzerAndFixerAsync()
{
var assemblies = new[]
{
await GenerateAssemblyAsync(
GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId"),
GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId"))
};

var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
var (analyzer, fixer) = Assert.Single(analyzersAndFixers);
var analyzerDiagnosticDescriptor = Assert.Single(analyzer.SupportedDiagnostics);
var fixerDiagnosticId = Assert.Single(fixer.FixableDiagnosticIds);
Assert.Equal(analyzerDiagnosticDescriptor.Id, fixerDiagnosticId);
}

[Fact]
public static async Task TestMultipleAnalyzersAndFixersAsync()
{
var assemblies = new[]
{
await GenerateAssemblyAsync(
GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId1"),
GenerateAnalyzerCode("DiagnosticAnalyzer2", "DiagnosticAnalyzerId2"),
GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId1"),
GenerateCodeFix("CodeFixProvider2", "DiagnosticAnalyzerId2"))
};

var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
Assert.Equal(2, analyzersAndFixers.Length);
Assert.Collection(analyzersAndFixers, VerifyAnalyzerCodeFixTuple, VerifyAnalyzerCodeFixTuple);
}

[Fact]
public static async Task TestMultipleAnalyzersAndFixersFromTwoAssembliesAsync()
{
var assemblies = new[]
{
await GenerateAssemblyAsync(
GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId1"),
GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId1")),
await GenerateAssemblyAsync(
GenerateAnalyzerCode("DiagnosticAnalyzer2", "DiagnosticAnalyzerId2"),
GenerateCodeFix("CodeFixProvider2", "DiagnosticAnalyzerId2")),
};
var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
Assert.Equal(2, analyzersAndFixers.Length);
Assert.Collection(analyzersAndFixers, VerifyAnalyzerCodeFixTuple, VerifyAnalyzerCodeFixTuple);
}

[Fact]
public static async Task NonMatchingIdsAsync()
{
var assemblies = new[]
{
await GenerateAssemblyAsync(
GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId"),
GenerateCodeFix("CodeFixProvider1", "CodeFixProviderId"))
};

var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
Assert.Empty(analyzersAndFixers);
}

[Fact]
public static async Task SomeMatchingIdsAsync()
{
var assemblies = new[]
{
await GenerateAssemblyAsync(
GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId1"),
GenerateAnalyzerCode("DiagnosticAnalyzer2", "DiagnosticAnalyzerId2"),
GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId1"),
GenerateCodeFix("CodeFixProvider2", "CodeFixProviderId"))
};

var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
var (analyzer, fixer) = Assert.Single(analyzersAndFixers);
var analyzerDiagnosticDescriptor = Assert.Single(analyzer.SupportedDiagnostics);
var fixerDiagnosticId = Assert.Single(fixer.FixableDiagnosticIds);
Assert.Equal(analyzerDiagnosticDescriptor.Id, fixerDiagnosticId);
}

[Fact]
public static async Task SingleIdMapstoMultipleFixersAsync()
{
var assemblies = new[]
{
await GenerateAssemblyAsync(
GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId1"),
GenerateAnalyzerCode("DiagnosticAnalyzer2", "DiagnosticAnalyzerId1"),
GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId1"),
GenerateCodeFix("CodeFixProvider2", "CodeFixProviderId"))
};

var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
Assert.Equal(2, analyzersAndFixers.Length);
Assert.Collection(analyzersAndFixers, VerifyAnalyzerCodeFixTuple, VerifyAnalyzerCodeFixTuple);
}

[Fact]
public static async Task MultipleIdsMaptoSingleFixerAsync()
{
var assemblies = new[]
{
await GenerateAssemblyAsync(
GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId1"),
GenerateAnalyzerCode("DiagnosticAnalyzer2", "DiagnosticAnalyzerId1"),
GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId1"))
};

var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
Assert.Equal(2, analyzersAndFixers.Length);
Assert.Collection(analyzersAndFixers, VerifyAnalyzerCodeFixTuple, VerifyAnalyzerCodeFixTuple);
}

private static void VerifyAnalyzerCodeFixTuple((DiagnosticAnalyzer Analyzer, CodeFixProvider Fixer) tuple)
{
var analyzerDiagnosticDescriptor = Assert.Single(tuple.Analyzer.SupportedDiagnostics);
var fixerDiagnosticId = Assert.Single(tuple.Fixer.FixableDiagnosticIds);
Assert.Equal(analyzerDiagnosticDescriptor.Id, fixerDiagnosticId);
}
}
}
3 changes: 3 additions & 0 deletions tests/dotnet-format.UnitTests.csproj
Expand Up @@ -11,8 +11,11 @@
</PropertyGroup>

<ItemGroup>
<Compile Remove="binaries\**" />
<Compile Remove="projects\**" />
<EmbeddedResource Remove="binaries\**" />
<EmbeddedResource Remove="projects\**" />
<None Remove="binaries\**" />
<None Remove="projects\**" />
</ItemGroup>

Expand Down

0 comments on commit adf693c

Please sign in to comment.