I writed a SourceGenerator, but how do I test it?
Main issue is how to imitate GeneratorExecutionContext (or just Compilation inside it) which generator gets into Execute method. I think there is a proper way to make fake SyntaxTrees for unit testing, but I cant find it. There are many articles about source generators itself, but none of them explain how to test generators.
You should look at official Source Generators Cookbook
There is example from it:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
namespace GeneratorTests.Tests
{
[TestClass]
public class GeneratorTests
{
[TestMethod]
public void SimpleGeneratorTest()
{
// Create the 'input' compilation that the generator will act on
Compilation inputCompilation = CreateCompilation(#"
namespace MyCode
{
public class Program
{
public static void Main(string[] args)
{
}
}
}
");
// directly create an instance of the generator
// (Note: in the compiler this is loaded from an assembly, and created via reflection at runtime)
CustomGenerator generator = new CustomGenerator();
// Create the driver that will control the generation, passing in our generator
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
// Run the generation pass
// (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls)
driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics);
// We can now assert things about the resulting compilation:
Debug.Assert(diagnostics.IsEmpty); // there were no diagnostics created by the generators
Debug.Assert(outputCompilation.SyntaxTrees.Count() == 2); // we have two syntax trees, the original 'user' provided one, and the one added by the generator
Debug.Assert(outputCompilation.GetDiagnostics().IsEmpty); // verify the compilation with the added source has no diagnostics
// Or we can look at the results directly:
GeneratorDriverRunResult runResult = driver.GetRunResult();
// The runResult contains the combined results of all generators passed to the driver
Debug.Assert(runResult.GeneratedTrees.Length == 1);
Debug.Assert(runResult.Diagnostics.IsEmpty);
// Or you can access the individual results on a by-generator basis
GeneratorRunResult generatorResult = runResult.Results[0];
Debug.Assert(generatorResult.Generator == generator);
Debug.Assert(generatorResult.Diagnostics.IsEmpty);
Debug.Assert(generatorResult.GeneratedSources.Length == 1);
Debug.Assert(generatorResult.Exception is null);
}
private static Compilation CreateCompilation(string source)
=> CSharpCompilation.Create("compilation",
new[] { CSharpSyntaxTree.ParseText(source) },
new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
}
}
In addition to the Source Generators Cookbook mentioned in the other answer:
The cookbook solution allows you to generate some code and then compare your results to expected, also check for warnings and compilation exceptions etc.
Now, you can additionally EXECUTE the generated code to make sure it's running correctly. For that change the project reference in the test-project like this:
<ProjectReference Include="..\MyGenerator\MyGenerator.csproj"
ReferenceOutputAssembly="true"
OutputItemType="Analyzer" />
And then simply call the generated code from your unit tests, like you would in the consumer project.
Related
Suppose my code possesses the knowledge about the metadata of a
nonexistent class library "mytest.dll", such as the types in this library, the functions of the types, the parameters and return types of the functions, etc.
How does my code manufacture this DLL using techniques such as reflection?
I know my code can generate the "mytest.cs" text file, then execute the compiler to produce the DLL, then delete the "mytest.cs" file. Just want to know if there are "more advanced" or "cooler" ways to do it.
Thanks.
There are 4 main steps in the process to compile and execute dynamic .net scripts from your application, even really complex scenarios can be simplified in this way:
Generate the code
Compile the script
Load the assembly
Execute the code
Lets generate a simple Hello Generated C# World App right now!:
Create a method that will generate an assembly that has 1 class called HelloWorldApp, this class has 1 method called GenerateMessage it will have X input parameters that will be integers, it will return a CSV string of the arguments that were passed in to it.
This solution requires the following package to be installed:
PM> Install-Package 'Microsoft.CodeAnalysis.CSharp.Scripting'
And will require the following using statements:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
Orchestration
The following method encapsulates the above steps:
private static void GenerateAndExecuteApp(int numberOfParameters)
{
string nameSpace = "Dynamic.Example";
string className = "HelloWorldApp";
string methodName = "GenerateMessage";
// 1. Generate the code
string script = BuildScript(nameSpace, className, methodName, numberOfParameters);
// 2. Compile the script
// 3. Load the Assembly
Assembly dynamicAssembly = CompileScript(script);
// 4. Execute the code
int[] arguments = Enumerable.Range(1, numberOfParameters).ToArray();
string message = ExecuteScript(dynamicAssembly, nameSpace, className, methodName, arguments);
Console.Out.WriteLine(message);
}
Generate the code
You say you already have item 1 sorted out, you can use StringBuilder, T4 templates or other mechanisms to generate the code files.
generating the code itself is its own question if you need help with that.
However, for our demo app, the following would work:
private static string BuildScript(string nameSpace, string className, string methodName, int numberOfParameters)
{
StringBuilder code = new StringBuilder();
code.AppendLine("using System;");
code.AppendLine("using System.Linq;");
code.AppendLine();
code.AppendLine($"namespace {nameSpace}");
code.AppendLine("{");
code.AppendLine($" public class {className}");
code.AppendLine(" {");
var parameterNames = Enumerable.Range(0, numberOfParameters).Select(x => $"p{x}").ToList();
code.Append($" public string {methodName}(");
code.Append(String.Join(",", parameterNames.Select(x => $"int {x}")));
code.AppendLine(")");
code.AppendLine(" {");
code.Append(" return $\"");
code.Append(String.Join(",", parameterNames.Select(x => $"{x}={{{x}}}")));
code.AppendLine("\";");
code.AppendLine(" }");
code.AppendLine(" }");
code.AppendLine("}");
return code.ToString();
}
For an input value of 3, the following code is generated:
using System;
using System.Linq;
namespace Dynamic.Example
{
public class HelloWorldApp
{
public string GenerateMessage(int p0,int p1,int p2)
{
return $"p0={p0},p1={p1},p2={p2}";
}
}
}
Compile the script (and Load it)
These are two discrete steps, however it is easiest to code them together in the same method, for this example we will ignore the generated dll and load the assembly directly into memory, that is generally the more likely use case for this type of scripting scenario anyway.
The hardest element of this is usually the referencing of the relevant dlls. There are a number of ways to achieve this, including loading all the dlls that are in the current executing context, I find a simple way to do this is to access the Assembly reference from the Type reference for the types we want to use inside the dynamic script:
List<string> dlls = new List<string> {
typeof(object).Assembly.Location,
typeof(Enumerable).Assembly.Location
};
Cut a long story short, this method compiles and loads the assembly into memory. It includes some crude compilation error handling, just to demonstrate how to do it:
private static Assembly CompileScript(string script)
{
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(script);
// use "mytest.dll" if you want, random works well enough
string assemblyName = System.IO.Path.GetRandomFileName();
List<string> dlls = new List<string> {
typeof(object).Assembly.Location,
typeof(Enumerable).Assembly.Location
};
MetadataReference[] references = dlls.Distinct().Select(x => MetadataReference.CreateFromFile(x)).ToArray();
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
// Now we actually compile the script, this includes some very crude error handling, just to show you can
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
List<string> errors = new List<string>();
foreach (Diagnostic diagnostic in failures)
{
//errors.AddDistinct(String.Format("{0} : {1}", diagnostic.Id, diagnostic.Location, diagnostic.GetMessage()));
errors.Add(diagnostic.ToString());
}
throw new ApplicationException("Compilation Errors: " + String.Join(Environment.NewLine, errors));
}
else
{
ms.Seek(0, SeekOrigin.Begin);
return Assembly.Load(ms.ToArray());
}
}
}
Execute the code
Finally, we can use reflection to instantiate an instance of the new app and then we can obtain a reference to the method and it. The name of the parameters is irrelevant, as long
we pass them through in the correct order:
for this demo the order is sort of irrelevant to, given they are all the same type ;)
private static string ExecuteScript(Assembly assembly, string nameSpace, string className, string methodName, int[] arguments)
{
var appType = assembly.GetType($"{nameSpace}.{className}");
object app = Activator.CreateInstance(appType);
MethodInfo method = appType.GetMethod(methodName);
object result = method.Invoke(app, arguments.Cast<object>().ToArray());
return result as string;
}
Output
The final output from all this for our method with 3 passed into it is:
p0=1,p1=2,p2=3
So that was super crude, you can bypass most of the indirect reflection aspects through the use of Interfaces. If your generated script inherits from types or interfaces that the calling code also has a strong reference to, then ExecuteScript in the above example might look like this:
private static string ExecuteScript(Assembly assembly, string nameSpace, string className)
{
var appType = assembly.GetType($"{nameSpace}.{className}");
object app = Activator.CreateInstance(appType);
if (app is KnownInterface known)
{
return known.GenerateMessage(1,2,3);
}
throw new NotSupportedException("Couldn't resolve known type");
}
The major benefit to using an interface or base class reference is that you can natively set properties or call other methods without having to reflect references to them all or to resort to using dynamic which would work, but becomes a bit harder to debug.
Of course the interface solution is hard to implement when we had a variable number of parameters, so that's not the best example, usually with dynamic scripts you would construct a known environment, say a known class and methods, but you might want to inject custom code into the body of the method.
It's a bit of fun in the end, but this simple example shows that C# can be used as a runtime scripting engine without too much trouble.
In the DotNetYaml sample code I'm looking at, there's a C# construct:
var deserializer = new Deserializer(namingConvention: new CamelCaseNamingConvention());
var order = deserializer.Deserialize<Order>(input);
What is the equivalent F# code? I've tried
let deserializer = new Deserializer(namingConvention=new CamelCaseNamingConvention())
deserializer.Deserialize<Meta>(input)
If you have a C# library that defines optional parameters, then you can use the syntax you are using in your question. To quickly show that's the case, I compiled the following C# code as a library:
using System;
namespace Demo {
public class MyClass {
public static void Foo(int first, string second = "foo", string third = "bar") { }
}
}
You can reference this and use it from F# as follows:
open Demo
MyClass.Foo(1, third="hi")
I tried to do this with YamlDotNet which, I guess, is the library that you were using, but I get an error that the Deserializer class does not have namingConvention as an argument, so my guess would be that you are probably using a different version of the library than you are thinking (or perhaps, my guess of what library you're using was wrong...).
I have these requirements coming from client every week for some new logic or verification. For which I have to code new logic (basically some if-else and loops) and launch a new build for him. I want to avoid it by simply coding my logic in visual studio then writing a utility to export it to XML or something and send it to client via e-mail. He just have to place this file in some appropriate folder and the application will behave considering this logic.
Please suggest some solutions. My platform is C# Asp.Net.
Thanks
Using .NET 4.6 and the NuGetPackage Microsoft.CodeAnalysis.Scripting you could implement a scripting engine to run your c# code residing in a textfile without building an assembly.
Install NuGet Package:
Install-Package Microsoft.CodeAnalysis.Scripting.CSharp
Implement TestClass with some basic C#-Code-Content:
class Program
{
static void Main(string[] args)
{
TestScript();
}
private static async void TestScript()
{
// Code snippet: a class with one string-property.
string codeContent = #" using System;
public class ScriptedClass
{
public string HelloWorld { get; set; }
public ScriptedClass()
{
HelloWorld = ""Hello Roslyn!"";
}
}
new ScriptedClass().HelloWorld";
// Instanciate CSharpScriptEngine
var engine = new CSharpScriptEngine();
// Execute code and return string property (HelloWorld)
var scriptingState = await engine.ExecuteAsync(codeContent);
// Print return value from CSharpScript
Console.WriteLine("Returned from CSharpScript: {0}", scriptingState.ReturnValue);
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
}
Implement a ScriptingEngine:
internal sealed class CSharpScriptEngine
{
public async Task<ScriptState<object>> ExecuteAsync(string codeContent)
{
// Add references from calling assembly
ScriptOptions options = ScriptOptions.Default.AddReferences(Assembly.GetExecutingAssembly());
// Run codeContent with given options
return await CSharpScript.RunAsync(codeContent, options);
}
}
Read ScriptCode from textfile:
So basically you could read some csharpcode from a textfile of your choice and run them on the fly:
private static async void TestScript()
{
// Read in script file
string codeContent = File.ReadAllText(#"C:\Temp\CSharpScriptTest.cs");
var engine = new CSharpScriptEngine();
// Run script
var scriptingState = await engine.ExecuteAsync(codeContent);
Console.WriteLine("Returned from CSharpScript: {0}", scriptingState.ReturnValue);
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
In case you are wondering how all of this works under the hood, Roslyn will create a so called submission from your script code. A submission is an in memory assembly containing the types generated around your script code, which can be identified among the assemblies in the current AppDomain by a ℛ prefix in the name.
The precise implementation details are not important here (though, for example, scriptcs heavily relies on understanding in detail how Roslyn works to provide its extra features), but it's important to know that submissions can be chained together. When they are chained, variables, methods or classes defined in an earlier submission are available to use in subsequent submissions, creating a feature of a C# REPL (read-evaluate-print loop).
C# and Visual Basic - Use Roslyn to Write a Live Code Analyzer for Your API
Hope it helps
I'm trying to write some code to find all method invocations of any given method as I am looking to create an open source UML Sequence Diagramming tool. I'm having trouble, however, getting past the first few lines of code :/
The API appears to have changed drastically and I can't seem to infer proper usage by looking at the code.
When I do:
var workspace = new CustomWorkspace();
string solutionPath = #"C:\Workspace\RoslynTest\RoslynTest.sln";
var solution = workspace.CurrentSolution;
I find that workspace.CurrentSolution has 0 Projects. I figured this would be the equivalent to what was previously Workspace.LoadSolution( string solutionFile ) which would then supposedly contain any Projects in the Solution, but I am not finding any success with this path.
I am terribly confused 0.o
If someone could offer some additional guidance as to how I can use the FindReferences API to identify all invocations of a particular method, it would be very much appreciated!
Alternatively, would I be better off taking a static-analysis approach? I would like to support things like lambdas, iterator methods and async.
====================================================================
Edit -
Here is a full example based on the accepted answer:
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.FindSymbols;
using System.Diagnostics;
namespace RoslynTest
{
class Program
{
static void Main(string[] args)
{
string solutionPath = #"C:\Workspace\RoslynTest\RoslynTest.sln";
var workspace = MSBuildWorkspace.Create();
var solution = workspace.OpenSolutionAsync(solutionPath).Result;
var project = solution.Projects.Where(p => p.Name == "RoslynTest").First();
var compilation = project.GetCompilationAsync().Result;
var programClass = compilation.GetTypeByMetadataName("RoslynTest.Program");
var barMethod = programClass.GetMembers("Bar").First();
var fooMethod = programClass.GetMembers("Foo").First();
var barResult = SymbolFinder.FindReferencesAsync(barMethod, solution).Result.ToList();
var fooResult = SymbolFinder.FindReferencesAsync(fooMethod, solution).Result.ToList();
Debug.Assert(barResult.First().Locations.Count() == 1);
Debug.Assert(fooResult.First().Locations.Count() == 0);
}
public bool Foo()
{
return "Bar" == Bar();
}
public string Bar()
{
return "Bar";
}
}
}
CustomWorkspace is
A workspace that allows manual addition of projects and documents.
Since you're trying to load a solution, you should use the MSBuildWorkspace, which is
A workspace that can be populated by opening MSBuild solution and project files.
You can create a new MSBuildWorkspace and call OpenSolutionAsync with your solutionPath. For the reference finding part, take a look at the SymbolFinder.
Solutions are an MSBuild concept.
You need to create an MSBuildWorkspace and call OpenSolutionAsync().
string solutionPath = #"C:\Workspace\RoslynTest\RoslynTest.sln";
creates a local variable. It has no influence on your CustomWorkspace object.
Consider the follow code sample (copy-pastable in LINQPad):
void Main()
{
var ttNamespace = new CodeNamespace("SampleNamespace");
ttNamespace.Imports.AddRange(new[]{"System", "System.Text", "System.Threading"}.Select(x => new CodeNamespaceImport(x)).ToArray());
var newType = new CodeTypeDeclaration("SampleClass")
{
TypeAttributes = TypeAttributes.Public
};
var newField = new CodeMemberField(typeof(List<int>), "_field")
{
Attributes = MemberAttributes.Public
};
newField.InitExpression = new CodeObjectCreateExpression(new CodeTypeReference(typeof(List<int>)), new CodeExpression[] { });
newType.Members.Add(newField);
ttNamespace.Types.Add(newType);
var parameters = new CompilerParameters()
{
GenerateExecutable = false,
OutputAssembly = #"C:\test.dll"
};
var ccUnit = new CodeCompileUnit();
ccUnit.Namespaces.Add(ttNamespace);
var result = CodeDomProvider.CreateProvider("C#").CompileAssemblyFromDom(parameters, ccUnit);
Console.WriteLine (result.Errors);
}
As you can see, I explicitly add namespaces System, System.Text and System.Threading. I also utilize the type List<int> but don't provide a namespace import for System.Collections.Generic.
Logically this should result in an error somewhere down the line. Maybe not at compile time, but definitely at runtime. This is not the case though, the code compiles to this:
We can see that all using statements have already been organized for us: redundant ones are left out and required ones are added. Obviously this is very useful but this begs the question:
What is the point of explicit CodeNamespaceImport statements when the compiler doesn't use it anyway?
I should note that completely omitting the adding of my namespaces doesn't change the output at all: it keeps working as it is.
There is no point, typeof(List<int>) is unambiguous in your program and can generate the full type name of List. It isn't very clear where you got the source listing from, I suspect you used a disassembler that automatically figured out which using directives where most useful.
You can modify the program like this to see what the compiler really sees:
var prov = CodeDomProvider.CreateProvider("C#");
var gen = prov.CreateGenerator();
using (var writer = new System.IO.StreamWriter("c:\\temp\\test.cs")) {
gen.GenerateCodeFromCompileUnit(ccUnit, writer, new CodeGeneratorOptions());
}
var result = prov.CompileAssemblyFromDom(parameters, ccUnit);
Which produces:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.34011
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace SampleNamespace {
using System;
using System.Text;
using System.Threading;
public class SampleClass {
public System.Collections.Generic.List<int> _field = new System.Collections.Generic.List<int>();
}
}
As you can tell, the compiler actually sees the full type name and the using directives are not necessary.
At the IL level, there are no namespace imports, all types are always referenced using their full name.
So, when a decompiler generates C# from IL, it doesn't know what namespace imports did the original code contain. This means that a reasonable decompiler will probably generate code that imports all namespaces that are used in a class (unless that would cause conflicts).