Giter Site home page Giter Site logo

gu.roslyn.asserts's Introduction

Gu.Roslyn.Asserts

License NuGet Build status

Microsoft are working on an official package for testing analyzers: Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.

Hopefully this library will not be needed in the future.

Asserts for testing Roslyn analyzers.

Use 1.x for Microsoft.CodeAnalysis 1.x

RoslynAssert.Valid

Use RoslynAssert.Valid<NoErrorAnalyzer>(code) to test that an analyzer does not report errors for valid code. The code is checked so that it does not have any compiler errors either. A typical test fixture looks like:

public class ValidCode
{
    private static readonly DiagnosticAnalyzer Analyzer = new YourAnalyzer();

    [Test]
    public void SomeTest()
    {
        var code = @"
namespace TestCode
{
    class Foo
    {
    }
}";
        RoslynAssert.Valid(YourAnalyzer, code);
    }
    ...
}

If the analyzer produces many diagnostics you can pass in a descriptor so that only diagnostics matching it are checked.

public class ValidCode
{
    private static readonly DiagnosticAnalyzer Analyzer = new YourAnalyzer();
    private static readonly DiagnosticDescriptor Descriptor = YourAnalyzer.SomeDescriptor;

    [Test]
    public void SomeTest()
    {
        var code = @"
namespace TestCode
{
    class Foo
    {
    }
}";
        RoslynAssert.Valid(YourAnalyzer, Descriptor, code);
    }
    ...
}

When testing all analyzers something like this can be used:

public class ValidCodeWithAllAnalyzers
{
    private static readonly IReadOnlyList<DiagnosticAnalyzer> AllAnalyzers = typeof(KnownSymbol)
                                                                                .Assembly.GetTypes()
                                                                                .Where(typeof(DiagnosticAnalyzer).IsAssignableFrom)
                                                                                .Select(t => (DiagnosticAnalyzer)Activator.CreateInstance(t))
                                                                                .ToArray();


    private static readonly Solution ValidCodeProjectSln = CodeFactory.CreateSolution(
        ProjectFile.Find("ValidCode.csproj"),
        AllAnalyzers,
        RoslynAssert.MetadataReferences);

    [TestCaseSource(nameof(AllAnalyzers))]
    public void ValidCodeProject(DiagnosticAnalyzer analyzer)
    {
        RoslynAssert.Valid(analyzer, ValidCodeProjectSln);
    }
}

RoslynAssert.Diagnostics

Use RoslynAssert.Diagnostics<FieldNameMustNotBeginWithUnderscore>(code) to test that the analyzer reports error or warning at position indicated with ↓ With an aplhanumeric keyboard alt + 25 writes .

A typical test fixture looks like:

public class Diagnostics
{
    private static readonly DiagnosticAnalyzer Analyzer = new YourAnalyzer();
    private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(YourAnalyzer.Descriptor);

    [Test]
    public void SomeTest()
    {
        var code = @"
namespace TestCode
{
    class ↓Foo
    {
    }
}";
        RoslynAssert.Diagnostics(YourAnalyzer, code);
    }

    [Test]
    public void CheckMessageAlso()
    {
        var code = @"
namespace TestCode
{
    class ↓Foo
    {
    }
}";
        RoslynAssert.Diagnostics(YourAnalyzer, ExpectedDiagnostic.WithMessage("Don't name it foo"), code);
    }
    ...
}

If the analyzer produces many diagnostics you can pass in a descriptor so that only diagnostics matching it are checked.

public class Diagnostics
{
    private static readonly DiagnosticAnalyzer Analyzer = new YourAnalyzer();
    private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(YourAnalyzer.Descriptor);

    [Test]
    public void SomeTest()
    {
        var code = @"
namespace TestCode
{
    class ↓Foo
    {
    }
}";
        RoslynAssert.Diagnostics(YourAnalyzer, ExpectedDiagnostic, code);
    }
    ...
}

If the analyzer supports many diagnostics the overload with ExpectedDiagnostic must be used. This suppresses all diagnsics other than the expected.

CodeFix

Test that the analyzer reports an error or warning at position indicated with ↓ and that the codefix fixes it and produces the expected code. With an aplhanumeric keyboard alt + 25 writes .

[Test]
public void TestThatAnalyzerWarnsOnCorrectPositionAndThatCodeFixProducesExpectedCode()
{
    var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int ↓_value;
    }
}";

    var fixedCode = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int value;
    }
}";
    RoslynAssert.CodeFix<FieldNameMustNotBeginWithUnderscore, SA1309CodeFixProvider>(code, fixedCode);
}

A typical test fixture looks like:

public class CodeFix
{
    private static readonly DiagnosticAnalyzer Analyzer = new YourAnalyzer();
    private static readonly CodeFixProvider Fix = new YorCodeFixProvider();
    private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(YourAnalyzer.Descriptor);

    [Test]
    public void SomeTest()
    {
        var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int ↓_value;
    }
}";

        var fixedCode = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int value;
    }
}";
        RoslynAssert.CodeFix(Analyzer, Fix, code, fixedCode);
    }

    [Test]
    public void ExplicitFixTitle()
    {
        var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int ↓_value;
    }
}";

        var fixedCode = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int value;
    }
}";
        RoslynAssert.CodeFix(Analyzer, Fix, code, fixedCode, fixTitle: "Don't use underscore prefix");
    }
    ...
}

With explicit title for the fix to apply. Useful when there are many candidate fixes.

If the analyzer supports many diagnostics the overload with ExpectedDiagnostic must be used. This suppresses all diagnsics other than the expected.

Code fix only

When the code fix is for a warning produced by an analyzer that you do not own, for example a built in analyzer in Visual Studio.

[Test]
public void TestThatCodeFixProducesExpectedCode()
{
    var code = @"
namespace RoslynSandbox
{
    using System;

    public class Foo
    {
        public event EventHandler ↓Bar;
    }
}";

    var fixedCode = @"
namespace RoslynSandbox
{
    using System;

    public class Foo
    {
    }
}";
    RoslynAssert.CodeFix<RemoveUnusedFixProvider>("CS0067", code, fixedCode);
}

FixAll

When there are many isses that will be fixed:

[Test]
public void TestThatAnalyzerWarnsOnCorrectPositionAndThatCodeFixProducesExpectedCode()
{
    var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int ↓_value1;
        private readonly int ↓_value2;
    }
}";

    var fixedCode = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int value1;
        private readonly int value2;
    }
}";
    RoslynAssert.FixAll<FieldNameMustNotBeginWithUnderscore, SA1309CodeFixProvider>(code, fixedCode);
}

NoFix

Test that the analyzer reports an error or warning at position indicated with ↓ and that the codefix does not change the code. With an aplhanumeric keyboard alt + 25 writes . This can happen if for example it is decided to not support rare edge cases with the code fix.

[Test]
public void TestThatAnalyzerWarnsOnCorrectPositionAndThatCodeFixDoesNothing()
{
    var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int ↓_value;
    }
}";

    RoslynAssert.NoFix<FieldNameMustNotBeginWithUnderscore, SA1309CodeFixProvider>(code);
}

Refactoring

        [Test]
        public void WithPositionIndicated()
        {
            var testCode = @"
class ↓Foo
{
}";

            var fixedCode = @"
class FOO
{
}";

            RoslynAssert.Refactoring(new ClassNameToUpperCaseRefactoringProvider(), testCode, fixedCode);
			// Or if you want to assert on title also
			RoslynAssert.Refactoring(new ClassNameToUpperCaseRefactoringProvider(), testCode, fixedCode, title: "Change to uppercase.");
        }

AST

For checking every node and token in the tree.

[Test]
public void CheckAst()
{
    var actual = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName("a"), SyntaxFactory.IdentifierName("b"));
    var expected = CSharpSyntaxTree.ParseText("var c = a + b").FindAssignmentExpression("a + b");
    RoslynAssert.Ast(expected, actual);
}

SyntaxFactoryWriter

Get a string with a call to SyntaxFactory for generating the code passed in.

var code = @"namespace A.B
{
    public class C
    {
    }
}";
var call = SyntaxFactoryWriter.Serialize(code);

Attributes

When creating the workspace to analyze metadata references need to be added. There are a couple of ways to provide them using this library. Some overloads of the asserts allow passing explicit references but it will be verbose to do that everywhere.

In most scenarios something like this in the test project is what you want:

using Gu.Roslyn.Asserts;

[assembly: TransitiveMetadataReferences(
    typeof(Microsoft.EntityFrameworkCore.DbContext),
    typeof(Microsoft.AspNetCore.Mvc.Controller))]

MetadataReferenceAttribute

For specifying a metadata reference to be used in the tests, with or without aliases.

[assembly: MetadataReference(typeof(object), new[] { "global", "corlib" })]

MetadataReferencesAttribute

For specifying a batch of metadata references to be used in the tests.

[assembly: MetadataReferences(
    typeof(System.Linq.Enumerable),
    typeof(System.Net.WebClient),
    typeof(System.Reactive.Disposables.SerialDisposable),
    typeof(System.Reactive.Disposables.ICancelable),
    typeof(System.Reactive.Linq.Observable),
    typeof(Gu.Reactive.Condition),
    typeof(Gu.Wpf.Reactive.ConditionControl),
    typeof(System.Xml.Serialization.XmlSerializer),
    typeof(System.Windows.Media.Matrix),
    typeof(Microsoft.CodeAnalysis.CSharp.CSharpCompilation),
    typeof(Microsoft.CodeAnalysis.Compilation),
    typeof(NUnit.Framework.Assert))]

Calling RoslynAssert.ResetMetadataReferences() resets RoslynAssert.MetadataReferences to the list provided via the attribute or clears it if no attribute is provided.

MetadataReferences

For getting all metadata references specified with attributes use:

var compilation = CSharpCompilation.Create(
    "TestProject",
    new[] { syntaxTree },
    MetadataReferences.FromAttributes());

Sample AssemblyInfo.cs (for the test project.)

using System.Reflection;
using System.Runtime.InteropServices;
using Gu.Roslyn.Asserts;

...
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

[assembly: MetadataReference(typeof(object), new[] { "global", "corlib" })]
[assembly: MetadataReference(typeof(System.Diagnostics.Debug), new[] { "global", "system" })]
[assembly: MetadataReferences(
    typeof(System.Linq.Enumerable),
    typeof(System.Net.WebClient),
    typeof(System.Data.Common.DbConnection),
    typeof(System.Reactive.Disposables.SerialDisposable),
    typeof(System.Reactive.Disposables.ICancelable),
    typeof(System.Reactive.Linq.Observable),
    typeof(System.Xml.Serialization.XmlSerializer),
    typeof(System.Windows.Media.Brush),
    typeof(System.Windows.Controls.Control),
    typeof(System.Windows.Media.Matrix),
    typeof(System.Xaml.XamlLanguage),
    typeof(Microsoft.CodeAnalysis.CSharp.CSharpCompilation),
    typeof(Microsoft.CodeAnalysis.Compilation),
    typeof(NUnit.Framework.Assert))]

Exlicit set RoslynAssert.MetadataReferences

RoslynAssert.MetadataReferences.Add(MetadataReference.CreateFromFile(typeof(int).Assembly.Location));

A helper like this can be used.

private static IReadOnlyList<MetadataReference> CreateMetadataReferences(params Type[] types)
{
    return types.Select(type => type.GetTypeInfo().Assembly)
                .Distinct()
                .Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
                .ToArray();
}

IgnoredErrorsAttribute

For globally ignoring compiler warnings and errors introduced by code fixes when calling calling RoslynAssert.CodeFix and RoslynAssert.FixAll.

[assembly: IgnoredErrors("CS1569", ...)]

AllowedDiagnosticsAttribute

For globally ignoring compiler warnings and errors introduced by code fixes when calling calling RoslynAssert.CodeFix and RoslynAssert.FixAll.

[assembly: AllowedDiagnostics(AllowedDiagnostics.Warnings)]

Analyze

GetDiagnosticsAsync

Analyze a cs, csproj or sln file on disk.

[Test]
public async Task GetDiagnosticsFromProjectOnDisk()
{
    var dllFile = new Uri(Assembly.GetExecutingAssembly().CodeBase, UriKind.Absolute).LocalPath;
    Assert.AreEqual(true, CodeFactory.TryFindProjectFile(new FileInfo(dllFile), out FileInfo projectFile));
    var diagnostics = await Analyze.GetDiagnosticsAsync(new FieldNameMustNotBeginWithUnderscore(), projectFile, MetadataReferences)
                                    .ConfigureAwait(false);
    ...
}

Fix

When dropping down to manual mode Analyze & Fix can be used like this:

[Test]
public void SingleClassOneErrorCorrectFix()
{
    var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int _value;
    }
}";

    var fixedCode = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int value;
    }
}";
    var analyzer = new FieldNameMustNotBeginWithUnderscore();
    var cSharpCompilationOptions = CodeFactory.DefaultCompilationOptions(analyzer);
    var metadataReferences = new[] { MetadataReference.CreateFromFile(typeof(int).Assembly.Location) };
    var sln = CodeFactory.CreateSolution(code, cSharpCompilationOptions, metadataReferences);
    var diagnostics = Analyze.GetDiagnostics(sln, analyzer);
    var fixedSln = Fix.Apply(sln, new DontUseUnderscoreCodeFixProvider(), diagnostics);
    CodeAssert.AreEqual(fixedCode, fixedSln.Projects.Single().Documents.Single());
}

CodeFactory

CreateSolution

Create a Microsoft.CodeAnalysis.AdhocWorkspace, a Roslyn Solution from code.

[Test]
public void CreateSolutionFromSources()
{
    var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int _value;
    }
}";
    var sln = CodeFactory.CreateSolution(code, new[] { new FieldNameMustNotBeginWithUnderscore() });
    Assert.AreEqual("RoslynSandbox", sln.Projects.Single().Name);
    Assert.AreEqual("Foo.cs", sln.Projects.Single().Documents.Single().Name);
}

[Test]
public void CreateSolutionFromSources()
{
    var code1 = @"
namespace Project1
{
    class Foo1
    {
        private readonly int _value;
    }
}";

    var code2 = @"
namespace Project2
{
    class Foo2
    {
        private readonly int _value;
    }
}";
    var sln = CodeFactory.CreateSolution(new[] { code1, code2 }, new[] { new FieldNameMustNotBeginWithUnderscore() });
    CollectionAssert.AreEqual(new[] { "Project1", "Project2" }, sln.Projects.Select(x => x.Name));
    Assert.AreEqual(new[] { "Foo1.cs", "Foo2.cs" }, sln.Projects.Select(x => x.Documents.Single().Name));
}

Create a Microsoft.CodeAnalysis.AdhocWorkspace, a Roslyn Solution from a file on disk.

[Test]
public void CreateSolutionFromProjectFile()
{
    Assert.AreEqual(
        true,
        CodeFactory.TryFindProjectFile(
            new FileInfo(new Uri(Assembly.GetExecutingAssembly().CodeBase, UriKind.Absolute).LocalPath),
            out FileInfo projectFile));
    var solution = CodeFactory.CreateSolution(
        projectFile,
        new[] { new FieldNameMustNotBeginWithUnderscore(), },
        CreateMetadataReferences(typeof(object)));
}

[Test]
public void CreateSolutionFromSolutionFile()
{
    Assert.AreEqual(
        true,
        CodeFactory.TryFindFileInParentDirectory(
            new FileInfo(new Uri(Assembly.GetExecutingAssembly().CodeBase, UriKind.Absolute).LocalPath).Directory, "Gu.Roslyn.Asserts.sln",
            out FileInfo solutionFile));
    var solution = CodeFactory.CreateSolution(
        solutionFile,
        new[] { new FieldNameMustNotBeginWithUnderscore(), },
        CreateMetadataReferences(typeof(object)));
}

Benchmark

Sample benchmark using BenchmarkDotNet.

public class FieldNameMustNotBeginWithUnderscoreBenchmark
{
    private static readonly Solution Solution = CodeFactory.CreateSolution(
        CodeFactory.FindSolutionFile("Gu.Roslyn.Asserts.sln"),
        MetadataReferences.Transitive(typeof(Benchmark).Assembly).ToArray());

    private static readonly Benchmark Benchmark = Benchmark.Create(Solution, new FieldNameMustNotBeginWithUnderscore());

    [BenchmarkDotNet.Attributes.Benchmark]
    public void RunOnGuRoslynAssertsSln()
    {
        Benchmark.Run();
    }
}

SyntaxNodeExt

[Test]
public void FindAssignmentExpressionDemo()
{
    var syntaxTree = CSharpSyntaxTree.ParseText(
        @"
namespace RoslynSandbox
{
    internal class Foo
    {
        internal Foo()
        {
            var temp = 1;
            temp = 2;
        }
    }
}");
    var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), });
    var semanticModel = compilation.GetSemanticModel(syntaxTree);
    var assignment = syntaxTree.FindAssignmentExpression("temp = 2");
    Assert.AreEqual("temp = 2", assignment.ToString());
    Assert.AreEqual("int", semanticModel.GetTypeInfo(assignment.Right).Type.ToDisplayString());
}

AstView

Animation

Usage with different test project types

Net472 new project type.

<PropertyGroup>
  <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
</PropertyGroup>

NetCoreApp2.0

TODO figure out what is needed here.

gu.roslyn.asserts's People

Contributors

forki avatar johanlarsson avatar mikkelbu avatar milleniumbug avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.