From e4fdc80462658ac67b3dbf44dded20978f3a8b9b Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Wed, 4 Oct 2023 15:40:06 -0700 Subject: [PATCH 01/43] Sketch source generators. Signed-off-by: Phillip Hoff --- all.sln | 31 +++++++ .../ActorClient/ActorClient.csproj | 22 +++++ .../GeneratedActor/ActorClient/Program.cs | 72 ++++++++++++++++ .../ActorCommon/ActorCommon.csproj | 20 +++++ .../ActorCommon/IMyPublicActor.cs | 18 ++++ .../ActorCommon/MyPublicActorManualProxy.cs | 24 ++++++ .../ActorGenerator/ActorClientGenerator.cs | 84 +++++++++++++++++++ .../ActorGenerator/ActorGenerator.csproj | 23 +++++ .../ActorService/ActorService.csproj | 20 +++++ .../ActorService/MyPublicActor.cs | 36 ++++++++ .../GeneratedActor/ActorService/Program.cs | 60 +++++++++++++ .../Properties/launchSettings.json | 31 +++++++ .../ActorService/appsettings.Development.json | 8 ++ .../ActorService/appsettings.json | 9 ++ 14 files changed, 458 insertions(+) create mode 100644 examples/GeneratedActor/ActorClient/ActorClient.csproj create mode 100644 examples/GeneratedActor/ActorClient/Program.cs create mode 100644 examples/GeneratedActor/ActorCommon/ActorCommon.csproj create mode 100644 examples/GeneratedActor/ActorCommon/IMyPublicActor.cs create mode 100644 examples/GeneratedActor/ActorCommon/MyPublicActorManualProxy.cs create mode 100644 examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs create mode 100644 examples/GeneratedActor/ActorGenerator/ActorGenerator.csproj create mode 100644 examples/GeneratedActor/ActorService/ActorService.csproj create mode 100644 examples/GeneratedActor/ActorService/MyPublicActor.cs create mode 100644 examples/GeneratedActor/ActorService/Program.cs create mode 100644 examples/GeneratedActor/ActorService/Properties/launchSettings.json create mode 100644 examples/GeneratedActor/ActorService/appsettings.Development.json create mode 100644 examples/GeneratedActor/ActorService/appsettings.json diff --git a/all.sln b/all.sln index 47fc9098c..081411ca5 100644 --- a/all.sln +++ b/all.sln @@ -104,6 +104,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BulkPublishEventExample", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowUnitTest", "examples\Workflow\WorkflowUnitTest\WorkflowUnitTest.csproj", "{8CA09061-2BEF-4506-A763-07062D2BD6AC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GeneratedActor", "GeneratedActor", "{7592AFA4-426B-42F3-AE82-957C86814482}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorClient", "examples\GeneratedActor\ActorClient\ActorClient.csproj", "{61C24126-F39D-4BEA-96DC-FC87BA730554}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorCommon", "examples\GeneratedActor\ActorCommon\ActorCommon.csproj", "{CB903D21-4869-42EF-BDD6-5B1CFF674337}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorGenerator", "examples\GeneratedActor\ActorGenerator\ActorGenerator.csproj", "{980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorService", "examples\GeneratedActor\ActorService\ActorService.csproj", "{7C06FE2D-6C62-48F5-A505-F0D715C554DE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -248,6 +258,22 @@ Global {DDC41278-FB60-403A-B969-2AEBD7C2D83C}.Release|Any CPU.Build.0 = Release|Any CPU {8CA09061-2BEF-4506-A763-07062D2BD6AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8CA09061-2BEF-4506-A763-07062D2BD6AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61C24126-F39D-4BEA-96DC-FC87BA730554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61C24126-F39D-4BEA-96DC-FC87BA730554}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61C24126-F39D-4BEA-96DC-FC87BA730554}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61C24126-F39D-4BEA-96DC-FC87BA730554}.Release|Any CPU.Build.0 = Release|Any CPU + {CB903D21-4869-42EF-BDD6-5B1CFF674337}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB903D21-4869-42EF-BDD6-5B1CFF674337}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB903D21-4869-42EF-BDD6-5B1CFF674337}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB903D21-4869-42EF-BDD6-5B1CFF674337}.Release|Any CPU.Build.0 = Release|Any CPU + {980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}.Debug|Any CPU.Build.0 = Debug|Any CPU + {980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}.Release|Any CPU.ActiveCfg = Release|Any CPU + {980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}.Release|Any CPU.Build.0 = Release|Any CPU + {7C06FE2D-6C62-48F5-A505-F0D715C554DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C06FE2D-6C62-48F5-A505-F0D715C554DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C06FE2D-6C62-48F5-A505-F0D715C554DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C06FE2D-6C62-48F5-A505-F0D715C554DE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -293,6 +319,11 @@ Global {4A175C27-EAFE-47E7-90F6-873B37863656} = {0EF6EA64-D7C3-420D-9890-EAE8D54A57E6} {DDC41278-FB60-403A-B969-2AEBD7C2D83C} = {0EF6EA64-D7C3-420D-9890-EAE8D54A57E6} {8CA09061-2BEF-4506-A763-07062D2BD6AC} = {BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9} + {7592AFA4-426B-42F3-AE82-957C86814482} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78} + {61C24126-F39D-4BEA-96DC-FC87BA730554} = {7592AFA4-426B-42F3-AE82-957C86814482} + {CB903D21-4869-42EF-BDD6-5B1CFF674337} = {7592AFA4-426B-42F3-AE82-957C86814482} + {980B5FD8-0107-41F7-8FAD-E4E8BAE8A625} = {7592AFA4-426B-42F3-AE82-957C86814482} + {7C06FE2D-6C62-48F5-A505-F0D715C554DE} = {7592AFA4-426B-42F3-AE82-957C86814482} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40} diff --git a/examples/GeneratedActor/ActorClient/ActorClient.csproj b/examples/GeneratedActor/ActorClient/ActorClient.csproj new file mode 100644 index 000000000..3c64e5498 --- /dev/null +++ b/examples/GeneratedActor/ActorClient/ActorClient.csproj @@ -0,0 +1,22 @@ + + + + Exe + net7.0 + 10.0 + enable + enable + + + + + + + + + + + + diff --git a/examples/GeneratedActor/ActorClient/Program.cs b/examples/GeneratedActor/ActorClient/Program.cs new file mode 100644 index 000000000..bad7a3a74 --- /dev/null +++ b/examples/GeneratedActor/ActorClient/Program.cs @@ -0,0 +1,72 @@ +using Dapr.Actors; +using Dapr.Actors.Client; +using GeneratedActor; + +var actorId = new ActorId("1"); +var actorType = "MyPublicActor"; + +try +{ + //await TestRemotedActorAsync(); +} +catch (Exception ex) +{ + Console.WriteLine(ex); +} + +try +{ + await TestNonRemotedActorAsync(); +} +catch (Exception ex) +{ + Console.WriteLine(ex); +} + +try +{ + await TestNonRemotedManualProxyAsync(); +} +catch (Exception ex) +{ + Console.WriteLine(ex); +} + +Console.WriteLine("Hello, World!"); + +/* +async Task TestRemotedActorAsync() +{ + Console.WriteLine("Testing remoted actor client..."); + + var client = ActorProxy.Create(actorId, actorType); + + var state = await client.GetStateAsync(); + + await client.SetStateAsync(new MyState("Hello, World!")); +} +*/ + +async Task TestNonRemotedActorAsync() +{ + Console.WriteLine("Testing non-remoted actor client..."); + + var client = ActorProxy.Create(actorId, actorType /*, new ActorProxyOptions { UseJsonSerialization = true } */); + + var state = await client.InvokeMethodAsync("GetStateAsync"); + + await client.InvokeMethodAsync("SetStateAsync", new MyState("Hello, World!")); +} + +async Task TestNonRemotedManualProxyAsync() +{ + Console.WriteLine("Testing non-remoted manual proxy..."); + + var proxy = ActorProxy.Create(actorId, actorType /*, new ActorProxyOptions { UseJsonSerialization = true } */); + + var client = new MyPublicActorManualProxy(proxy); + + var state = await client.GetStateAsync(); + + await client.SetStateAsync(new MyState("Hello, World!")); +} diff --git a/examples/GeneratedActor/ActorCommon/ActorCommon.csproj b/examples/GeneratedActor/ActorCommon/ActorCommon.csproj new file mode 100644 index 000000000..5ee4837bc --- /dev/null +++ b/examples/GeneratedActor/ActorCommon/ActorCommon.csproj @@ -0,0 +1,20 @@ + + + + net7.0 + 10.0 + enable + enable + + + + + + + + + + + diff --git a/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs b/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs new file mode 100644 index 000000000..93164bdc2 --- /dev/null +++ b/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs @@ -0,0 +1,18 @@ +using Dapr.Actors; + +namespace GeneratedActor; + +public sealed record MyState(string Name); + +/// +/// +/// +/// +/// Non-remoted invocations have a strict limit on a single argument; CancellationToken is not supported. +/// +public interface IMyPublicActor : IActor +{ + Task GetStateAsync(); + + Task SetStateAsync(MyState state); +} diff --git a/examples/GeneratedActor/ActorCommon/MyPublicActorManualProxy.cs b/examples/GeneratedActor/ActorCommon/MyPublicActorManualProxy.cs new file mode 100644 index 000000000..711b6f653 --- /dev/null +++ b/examples/GeneratedActor/ActorCommon/MyPublicActorManualProxy.cs @@ -0,0 +1,24 @@ +using Dapr.Actors; +using Dapr.Actors.Client; + +namespace GeneratedActor; + +public sealed class MyPublicActorManualProxy : IMyPublicActor +{ + private readonly ActorProxy actorProxy; + + public MyPublicActorManualProxy(ActorProxy actorProxy) + { + this.actorProxy = actorProxy; + } + + public Task GetStateAsync() + { + return this.actorProxy.InvokeMethodAsync("GetStateAsync"); + } + + public Task SetStateAsync(MyState state) + { + return this.actorProxy.InvokeMethodAsync("SetStateAsync", state); + } +} diff --git a/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs b/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs new file mode 100644 index 000000000..92fc684ac --- /dev/null +++ b/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs @@ -0,0 +1,84 @@ +using System.Diagnostics; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ActorGenerator; + +[Generator] +public sealed class ActorClientGenerator : ISourceGenerator +{ + private sealed class ActorInterfaceSyntaxReceiver : ISyntaxContextReceiver + { + private readonly List models = new(); + + public IEnumerable Models => this.models; + + #region ISyntaxContextReceiver Members + + public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + { + if (context.Node is not InterfaceDeclarationSyntax interfaceDeclarationSyntax || interfaceDeclarationSyntax.BaseList is null) + { + return; + } + + if (interfaceDeclarationSyntax.BaseList.Types.Any(t => t.Type.ToString() == "IActor")) + { + context.SemanticModel.GetDeclaredSymbol(interfaceDeclarationSyntax); + this.models.Add(interfaceDeclarationSyntax); + } + } + + #endregion + } + + #region ISourceGenerator Members + + public void Execute(GeneratorExecutionContext context) + { + if (context.SyntaxContextReceiver is not ActorInterfaceSyntaxReceiver actorInterfaceSyntaxReceiver) + { + return; + } + + foreach (var model in actorInterfaceSyntaxReceiver.Models) + { + var semanticModel = context.Compilation.GetSemanticModel(model.SyntaxTree); + + var symbol = semanticModel.GetDeclaredSymbol(model); + var actorInterfaceTypeName = symbol!.ToString(); + + var source = $@"// +using Dapr.Actors; +using Dapr.Actors.Client; + +namespace {"bar"} +{{ + public sealed class {actorInterfaceTypeName}ManualProxy : {actorInterfaceTypeName} + {{ + private readonly ActorProxy actorProxy; + + public {actorInterfaceTypeName}ManualProxy(ActorProxy actorProxy) + {{ + this.actorProxy = actorProxy; + }} + }} +}} +"; + // Add the source code to the compilation + context.AddSource($"bar.g.cs", source); + } + } + + public void Initialize(GeneratorInitializationContext context) + { + while (!Debugger.IsAttached) + { + System.Threading.Thread.Sleep(500); + } + + context.RegisterForSyntaxNotifications(() => new ActorInterfaceSyntaxReceiver()); + } + + #endregion +} \ No newline at end of file diff --git a/examples/GeneratedActor/ActorGenerator/ActorGenerator.csproj b/examples/GeneratedActor/ActorGenerator/ActorGenerator.csproj new file mode 100644 index 000000000..b0a65457a --- /dev/null +++ b/examples/GeneratedActor/ActorGenerator/ActorGenerator.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + 10.0 + enable + enable + + + + true + + + + + + + + + + + + diff --git a/examples/GeneratedActor/ActorService/ActorService.csproj b/examples/GeneratedActor/ActorService/ActorService.csproj new file mode 100644 index 000000000..60b541436 --- /dev/null +++ b/examples/GeneratedActor/ActorService/ActorService.csproj @@ -0,0 +1,20 @@ + + + + net7.0 + 10.0 + enable + enable + + + + + + + + + + + + + diff --git a/examples/GeneratedActor/ActorService/MyPublicActor.cs b/examples/GeneratedActor/ActorService/MyPublicActor.cs new file mode 100644 index 000000000..a5777e25e --- /dev/null +++ b/examples/GeneratedActor/ActorService/MyPublicActor.cs @@ -0,0 +1,36 @@ +using Dapr.Actors.Runtime; + +namespace GeneratedActor; + +internal sealed class MyPublicActor : Actor, IMyPublicActor +{ + private readonly ILogger logger; + + private MyState currentState = new("default"); + + public MyPublicActor(ActorHost host, ILogger logger) + : base(host) + { + this.logger = logger; + } + + #region IMyPublicActor Members + + public Task GetStateAsync() + { + this.logger.LogInformation("GetStateAsync called."); + + return Task.FromResult(this.currentState); + } + + public Task SetStateAsync(MyState state) + { + this.logger.LogInformation("SetStateAsync called."); + + this.currentState = state; + + return Task.CompletedTask; + } + + #endregion +} \ No newline at end of file diff --git a/examples/GeneratedActor/ActorService/Program.cs b/examples/GeneratedActor/ActorService/Program.cs new file mode 100644 index 000000000..98cc20c10 --- /dev/null +++ b/examples/GeneratedActor/ActorService/Program.cs @@ -0,0 +1,60 @@ +using GeneratedActor; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddActors( + options => + { + options.UseJsonSerialization = true; + options.Actors.RegisterActor(); + }); + +var app = builder.Build(); + +app.UseRouting(); + +#pragma warning disable ASP0014 +app.UseEndpoints( + endpoints => + { + endpoints.MapActorsHandlers(); + }); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +app.MapGet("/weatherforecast", () => +{ + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; +}) +.WithName("GetWeatherForecast") +.WithOpenApi(); + +app.Run(); + +record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} diff --git a/examples/GeneratedActor/ActorService/Properties/launchSettings.json b/examples/GeneratedActor/ActorService/Properties/launchSettings.json new file mode 100644 index 000000000..8fbb1f581 --- /dev/null +++ b/examples/GeneratedActor/ActorService/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:56372", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5226", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/examples/GeneratedActor/ActorService/appsettings.Development.json b/examples/GeneratedActor/ActorService/appsettings.Development.json new file mode 100644 index 000000000..ff66ba6b2 --- /dev/null +++ b/examples/GeneratedActor/ActorService/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/examples/GeneratedActor/ActorService/appsettings.json b/examples/GeneratedActor/ActorService/appsettings.json new file mode 100644 index 000000000..4d566948d --- /dev/null +++ b/examples/GeneratedActor/ActorService/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From eaffdcc90702bae8c42736bd62805d4ecc25a17f Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 5 Oct 2023 09:34:46 -0700 Subject: [PATCH 02/43] Create interface implementation skeleton. Signed-off-by: Phillip Hoff --- .../ActorGenerator/ActorClientGenerator.cs | 16 +++++++++++----- .../ActorGenerator/ActorGenerator.csproj | 6 +----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs b/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs index 92fc684ac..36387ad00 100644 --- a/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs +++ b/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs @@ -22,9 +22,9 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) return; } + // TODO: Better qualify the IActor check. if (interfaceDeclarationSyntax.BaseList.Types.Any(t => t.Type.ToString() == "IActor")) { - context.SemanticModel.GetDeclaredSymbol(interfaceDeclarationSyntax); this.models.Add(interfaceDeclarationSyntax); } } @@ -44,9 +44,15 @@ public void Execute(GeneratorExecutionContext context) foreach (var model in actorInterfaceSyntaxReceiver.Models) { var semanticModel = context.Compilation.GetSemanticModel(model.SyntaxTree); - var symbol = semanticModel.GetDeclaredSymbol(model); - var actorInterfaceTypeName = symbol!.ToString(); + + if (symbol is null) + { + continue; + } + + var actorInterfaceTypeName = symbol.Name; + var fullyQualifiedActorInterfaceTypeName = symbol.ToString(); var source = $@"// using Dapr.Actors; @@ -54,7 +60,7 @@ public void Execute(GeneratorExecutionContext context) namespace {"bar"} {{ - public sealed class {actorInterfaceTypeName}ManualProxy : {actorInterfaceTypeName} + public sealed class {actorInterfaceTypeName}ManualProxy : {fullyQualifiedActorInterfaceTypeName} {{ private readonly ActorProxy actorProxy; @@ -66,7 +72,7 @@ public sealed class {actorInterfaceTypeName}ManualProxy : {actorInterfaceTypeNam }} "; // Add the source code to the compilation - context.AddSource($"bar.g.cs", source); + context.AddSource($"{actorInterfaceTypeName}.g.cs", source); } } diff --git a/examples/GeneratedActor/ActorGenerator/ActorGenerator.csproj b/examples/GeneratedActor/ActorGenerator/ActorGenerator.csproj index b0a65457a..3e888330b 100644 --- a/examples/GeneratedActor/ActorGenerator/ActorGenerator.csproj +++ b/examples/GeneratedActor/ActorGenerator/ActorGenerator.csproj @@ -1,7 +1,7 @@ - net7.0 + netstandard2.0 10.0 enable enable @@ -11,10 +11,6 @@ true - - - - From 468fb59b7f4e447a651094ec9e7b8b2e24ddeaba Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 5 Oct 2023 10:49:15 -0700 Subject: [PATCH 03/43] Sketch skeletons of method implementations. Signed-off-by: Phillip Hoff --- .../ActorGenerator/ActorClientGenerator.cs | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs b/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs index 36387ad00..5ca0865e2 100644 --- a/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs +++ b/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs @@ -44,7 +44,7 @@ public void Execute(GeneratorExecutionContext context) foreach (var model in actorInterfaceSyntaxReceiver.Models) { var semanticModel = context.Compilation.GetSemanticModel(model.SyntaxTree); - var symbol = semanticModel.GetDeclaredSymbol(model); + var symbol = semanticModel.GetDeclaredSymbol(model) as INamedTypeSymbol; if (symbol is null) { @@ -54,6 +54,10 @@ public void Execute(GeneratorExecutionContext context) var actorInterfaceTypeName = symbol.Name; var fullyQualifiedActorInterfaceTypeName = symbol.ToString(); + var members = symbol.GetMembers().OfType().Where(m => m.MethodKind == MethodKind.Ordinary).ToList(); + + var methodImplementations = String.Join("\n", members.Select(GenerateMethodImplementation)); + var source = $@"// using Dapr.Actors; using Dapr.Actors.Client; @@ -68,6 +72,8 @@ public sealed class {actorInterfaceTypeName}ManualProxy : {fullyQualifiedActorIn {{ this.actorProxy = actorProxy; }} + + {methodImplementations} }} }} "; @@ -87,4 +93,37 @@ public void Initialize(GeneratorInitializationContext context) } #endregion + + private static string GenerateMethodImplementation(IMethodSymbol method) + { + string methodName = method.Name; + var returnType = method.ReturnType as INamedTypeSymbol; + + if (returnType is null) + { + // TODO: Return a diagnostic instead. + throw new InvalidOperationException("Return type is not a named type symbol."); + } + + var returnTypeArgument = returnType.TypeArguments.FirstOrDefault(); + + if (returnTypeArgument is null) + { + return $@" + public {returnType.ToString()} {methodName}() + {{ + return this.actorProxy.InvokeMethodAsync(""{methodName}""); + }} + "; + } + else + { + return $@" + public {returnType} {methodName}() + {{ + return this.actorProxy.InvokeMethodAsync<{returnTypeArgument.ToString()}>(""{methodName}""); + }} + "; + } + } } \ No newline at end of file From 8d7b62017aa10700bf652971400458db354ccd43 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 5 Oct 2023 11:09:31 -0700 Subject: [PATCH 04/43] Sketch method parameters. Signed-off-by: Phillip Hoff --- .../GeneratedActor/ActorGenerator/ActorClientGenerator.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs b/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs index 5ca0865e2..02869fa42 100644 --- a/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs +++ b/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs @@ -105,12 +105,14 @@ private static string GenerateMethodImplementation(IMethodSymbol method) throw new InvalidOperationException("Return type is not a named type symbol."); } + var parameterType = method.Parameters.FirstOrDefault(); + var returnTypeArgument = returnType.TypeArguments.FirstOrDefault(); if (returnTypeArgument is null) { return $@" - public {returnType.ToString()} {methodName}() + public {returnType.ToString()} {methodName}({(parameterType is not null ? $"{parameterType.Type.ToString()} {parameterType.Name}" : "")}) {{ return this.actorProxy.InvokeMethodAsync(""{methodName}""); }} @@ -119,7 +121,7 @@ private static string GenerateMethodImplementation(IMethodSymbol method) else { return $@" - public {returnType} {methodName}() + public {returnType} {methodName}({(parameterType is not null ? $"{parameterType.Type.ToString()} {parameterType.Name}" : "")}) {{ return this.actorProxy.InvokeMethodAsync<{returnTypeArgument.ToString()}>(""{methodName}""); }} From 27b34f592bf78c8869852b9e726c3f5bf403d3ea Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 5 Oct 2023 11:41:51 -0700 Subject: [PATCH 05/43] Sketch implementations for all parameter/return combinations. Signed-off-by: Phillip Hoff --- .../GeneratedActor/ActorClient/Program.cs | 22 ++++++++++ .../ActorGenerator/ActorClientGenerator.cs | 42 ++++++++++++++++--- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/examples/GeneratedActor/ActorClient/Program.cs b/examples/GeneratedActor/ActorClient/Program.cs index bad7a3a74..d75b4c86e 100644 --- a/examples/GeneratedActor/ActorClient/Program.cs +++ b/examples/GeneratedActor/ActorClient/Program.cs @@ -32,6 +32,15 @@ Console.WriteLine(ex); } +try +{ + await TestGeneratedProxyAsync(); +} +catch (Exception ex) +{ + Console.WriteLine(ex); +} + Console.WriteLine("Hello, World!"); /* @@ -70,3 +79,16 @@ async Task TestNonRemotedManualProxyAsync() await client.SetStateAsync(new MyState("Hello, World!")); } + +async Task TestGeneratedProxyAsync() +{ + Console.WriteLine("Testing generated manual proxy..."); + + var proxy = ActorProxy.Create(actorId, actorType /*, new ActorProxyOptions { UseJsonSerialization = true } */); + + var client = new bar.IMyPublicActorManualProxy(proxy); + + var state = await client.GetStateAsync(); + + await client.SetStateAsync(new MyState("Hello, World!")); +} diff --git a/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs b/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs index 02869fa42..b28afecf9 100644 --- a/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs +++ b/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs @@ -84,10 +84,12 @@ public sealed class {actorInterfaceTypeName}ManualProxy : {fullyQualifiedActorIn public void Initialize(GeneratorInitializationContext context) { + /* while (!Debugger.IsAttached) { System.Threading.Thread.Sleep(500); } + */ context.RegisterForSyntaxNotifications(() => new ActorInterfaceSyntaxReceiver()); } @@ -96,7 +98,6 @@ public void Initialize(GeneratorInitializationContext context) private static string GenerateMethodImplementation(IMethodSymbol method) { - string methodName = method.Name; var returnType = method.ReturnType as INamedTypeSymbol; if (returnType is null) @@ -105,27 +106,56 @@ private static string GenerateMethodImplementation(IMethodSymbol method) throw new InvalidOperationException("Return type is not a named type symbol."); } + if (method.Parameters.Length > 1) + { + throw new InvalidOperationException("Too many parameters."); + } + + string methodName = method.Name; + var parameterType = method.Parameters.FirstOrDefault(); var returnTypeArgument = returnType.TypeArguments.FirstOrDefault(); - if (returnTypeArgument is null) + if (returnTypeArgument is not null && parameterType is not null) + { + return $@" + public {returnType} {methodName}({parameterType.Type} {parameterType.Name}) + {{ + return this.actorProxy.InvokeMethodAsync<{parameterType.Type}, {returnTypeArgument}>(""{methodName}"", {parameterType.Name}); + }} + "; + } + else if (returnTypeArgument is null && parameterType is null) { return $@" - public {returnType.ToString()} {methodName}({(parameterType is not null ? $"{parameterType.Type.ToString()} {parameterType.Name}" : "")}) + public {returnType} {methodName}() {{ return this.actorProxy.InvokeMethodAsync(""{methodName}""); }} "; } - else + else if (returnTypeArgument is null && parameterType is not null) { return $@" - public {returnType} {methodName}({(parameterType is not null ? $"{parameterType.Type.ToString()} {parameterType.Name}" : "")}) + public {returnType} {methodName}({parameterType.Type} {parameterType.Name}) {{ - return this.actorProxy.InvokeMethodAsync<{returnTypeArgument.ToString()}>(""{methodName}""); + return this.actorProxy.InvokeMethodAsync(""{methodName}"", {parameterType.Name}); }} "; } + else if (returnTypeArgument is not null && parameterType is null) + { + return $@" + public {returnType} {methodName}() + {{ + return this.actorProxy.InvokeMethodAsync<{returnTypeArgument}>(""{methodName}""); + }} + "; + } + else + { + throw new InvalidOperationException("Unexpected case."); + } } } \ No newline at end of file From ee85ac46493ff28f3492e9eb0afe17e896452e9e Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 5 Oct 2023 12:57:32 -0700 Subject: [PATCH 06/43] Move generator to src/. Signed-off-by: Phillip Hoff --- examples/GeneratedActor/ActorClient/ActorClient.csproj | 2 +- examples/GeneratedActor/ActorCommon/ActorCommon.csproj | 2 +- .../Dapr.Actors.Generators}/ActorClientGenerator.cs | 5 +++++ .../Dapr.Actors.Generators/Dapr.Actors.Generators.csproj | 0 4 files changed, 7 insertions(+), 2 deletions(-) rename {examples/GeneratedActor/ActorGenerator => src/Dapr.Actors.Generators}/ActorClientGenerator.cs (93%) rename examples/GeneratedActor/ActorGenerator/ActorGenerator.csproj => src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj (100%) diff --git a/examples/GeneratedActor/ActorClient/ActorClient.csproj b/examples/GeneratedActor/ActorClient/ActorClient.csproj index 3c64e5498..4a11a7cf9 100644 --- a/examples/GeneratedActor/ActorClient/ActorClient.csproj +++ b/examples/GeneratedActor/ActorClient/ActorClient.csproj @@ -14,7 +14,7 @@ - diff --git a/examples/GeneratedActor/ActorCommon/ActorCommon.csproj b/examples/GeneratedActor/ActorCommon/ActorCommon.csproj index 5ee4837bc..69333942f 100644 --- a/examples/GeneratedActor/ActorCommon/ActorCommon.csproj +++ b/examples/GeneratedActor/ActorCommon/ActorCommon.csproj @@ -12,7 +12,7 @@ - diff --git a/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs similarity index 93% rename from examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs rename to src/Dapr.Actors.Generators/ActorClientGenerator.cs index b28afecf9..96a22d688 100644 --- a/examples/GeneratedActor/ActorGenerator/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -4,6 +4,9 @@ namespace ActorGenerator; +/// +/// Generates strongly-typed actor clients that use the non-remoting actor proxy. +/// [Generator] public sealed class ActorClientGenerator : ISourceGenerator { @@ -34,6 +37,7 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) #region ISourceGenerator Members + /// public void Execute(GeneratorExecutionContext context) { if (context.SyntaxContextReceiver is not ActorInterfaceSyntaxReceiver actorInterfaceSyntaxReceiver) @@ -82,6 +86,7 @@ public sealed class {actorInterfaceTypeName}ManualProxy : {fullyQualifiedActorIn } } + /// public void Initialize(GeneratorInitializationContext context) { /* diff --git a/examples/GeneratedActor/ActorGenerator/ActorGenerator.csproj b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj similarity index 100% rename from examples/GeneratedActor/ActorGenerator/ActorGenerator.csproj rename to src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj From 587629c38267250f249499fb8421d3ff060f9c59 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 5 Oct 2023 13:25:07 -0700 Subject: [PATCH 07/43] Make generation opt-in. Signed-off-by: Phillip Hoff --- .../ActorCommon/IMyPublicActor.cs | 5 +-- .../ActorClientGenerator.cs | 44 +++++++++++-------- .../GenerateActorClientAttribute.cs | 9 ++++ 3 files changed, 37 insertions(+), 21 deletions(-) create mode 100644 src/Dapr.Actors.Generators/GenerateActorClientAttribute.cs diff --git a/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs b/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs index 93164bdc2..52789362f 100644 --- a/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs +++ b/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs @@ -1,15 +1,14 @@ using Dapr.Actors; +using Dapr.Actors.Generators; namespace GeneratedActor; public sealed record MyState(string Name); -/// -/// -/// /// /// Non-remoted invocations have a strict limit on a single argument; CancellationToken is not supported. /// +[GenerateActorClient] public interface IMyPublicActor : IActor { Task GetStateAsync(); diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index 96a22d688..5cefef27c 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -10,26 +10,40 @@ namespace ActorGenerator; [Generator] public sealed class ActorClientGenerator : ISourceGenerator { + private const string AttributeText = @" + namespace Dapr.Actors.Generators + { + [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] + public sealed class GenerateActorClientAttribute : Attribute + { + } + }"; + private sealed class ActorInterfaceSyntaxReceiver : ISyntaxContextReceiver { - private readonly List models = new(); + private readonly List models = new(); - public IEnumerable Models => this.models; + public IEnumerable Models => this.models; #region ISyntaxContextReceiver Members public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { - if (context.Node is not InterfaceDeclarationSyntax interfaceDeclarationSyntax || interfaceDeclarationSyntax.BaseList is null) + if (context.Node is not InterfaceDeclarationSyntax interfaceDeclarationSyntax + || interfaceDeclarationSyntax.AttributeLists.Count == 0) { return; } - // TODO: Better qualify the IActor check. - if (interfaceDeclarationSyntax.BaseList.Types.Any(t => t.Type.ToString() == "IActor")) + var interfaceSymbol = context.SemanticModel.GetDeclaredSymbol(interfaceDeclarationSyntax) as INamedTypeSymbol; + + if (interfaceSymbol is null + || !interfaceSymbol.GetAttributes().Any(a => a.AttributeClass?.ToString() == "Dapr.Actors.Generators.GenerateActorClientAttribute")) { - this.models.Add(interfaceDeclarationSyntax); + return; } + + this.models.Add(interfaceSymbol); } #endregion @@ -45,20 +59,12 @@ public void Execute(GeneratorExecutionContext context) return; } - foreach (var model in actorInterfaceSyntaxReceiver.Models) + foreach (var interfaceSymbol in actorInterfaceSyntaxReceiver.Models) { - var semanticModel = context.Compilation.GetSemanticModel(model.SyntaxTree); - var symbol = semanticModel.GetDeclaredSymbol(model) as INamedTypeSymbol; - - if (symbol is null) - { - continue; - } - - var actorInterfaceTypeName = symbol.Name; - var fullyQualifiedActorInterfaceTypeName = symbol.ToString(); + var actorInterfaceTypeName = interfaceSymbol.Name; + var fullyQualifiedActorInterfaceTypeName = interfaceSymbol.ToString(); - var members = symbol.GetMembers().OfType().Where(m => m.MethodKind == MethodKind.Ordinary).ToList(); + var members = interfaceSymbol.GetMembers().OfType().Where(m => m.MethodKind == MethodKind.Ordinary).ToList(); var methodImplementations = String.Join("\n", members.Select(GenerateMethodImplementation)); @@ -96,6 +102,8 @@ public void Initialize(GeneratorInitializationContext context) } */ + context.RegisterForPostInitialization(i => i.AddSource("GenerateActorClientAttribute.g.cs", AttributeText)); + context.RegisterForSyntaxNotifications(() => new ActorInterfaceSyntaxReceiver()); } diff --git a/src/Dapr.Actors.Generators/GenerateActorClientAttribute.cs b/src/Dapr.Actors.Generators/GenerateActorClientAttribute.cs new file mode 100644 index 000000000..f231b5f8f --- /dev/null +++ b/src/Dapr.Actors.Generators/GenerateActorClientAttribute.cs @@ -0,0 +1,9 @@ +namespace Dapr.Actors; + +/// +/// Indicates an actor interface should have a strongly-typed client generated for it. +/// +[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] +public sealed class GenerateActorClientAttribute : Attribute +{ +} From 997ce6a3a631db1873ff9585beba7104a361478c Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 5 Oct 2023 14:28:28 -0700 Subject: [PATCH 08/43] Enable explicit client naming. Signed-off-by: Phillip Hoff --- .../ActorCommon/IMyPublicActor.cs | 3 ++- .../ActorClientGenerator.cs | 27 ++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs b/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs index 52789362f..a4a730b3d 100644 --- a/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs +++ b/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs @@ -1,3 +1,4 @@ +using System.Reflection.Metadata.Ecma335; using Dapr.Actors; using Dapr.Actors.Generators; @@ -8,7 +9,7 @@ public sealed record MyState(string Name); /// /// Non-remoted invocations have a strict limit on a single argument; CancellationToken is not supported. /// -[GenerateActorClient] +[GenerateActorClient(Name = "MyBestActorClient")] public interface IMyPublicActor : IActor { Task GetStateAsync(); diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index 5cefef27c..cb4e77424 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -11,11 +11,16 @@ namespace ActorGenerator; public sealed class ActorClientGenerator : ISourceGenerator { private const string AttributeText = @" + #nullable enable + namespace Dapr.Actors.Generators { [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] public sealed class GenerateActorClientAttribute : Attribute { + public string? Name { get; set; } + + public string? Namespace { get; set; } } }"; @@ -59,11 +64,18 @@ public void Execute(GeneratorExecutionContext context) return; } + var attributeSymbol = context.Compilation.GetTypeByMetadataName("Dapr.Actors.Generators.GenerateActorClientAttribute"); + foreach (var interfaceSymbol in actorInterfaceSyntaxReceiver.Models) { var actorInterfaceTypeName = interfaceSymbol.Name; var fullyQualifiedActorInterfaceTypeName = interfaceSymbol.ToString(); + var attributeData = interfaceSymbol.GetAttributes().Single(a => a.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) == true); + + var clientTypeName = GetClientName(interfaceSymbol, attributeData); + var namespaceName = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "Namespace").Value.Value?.ToString() ?? interfaceSymbol.ContainingNamespace.ToDisplayString(); + var members = interfaceSymbol.GetMembers().OfType().Where(m => m.MethodKind == MethodKind.Ordinary).ToList(); var methodImplementations = String.Join("\n", members.Select(GenerateMethodImplementation)); @@ -72,13 +84,13 @@ public void Execute(GeneratorExecutionContext context) using Dapr.Actors; using Dapr.Actors.Client; -namespace {"bar"} +namespace {namespaceName} {{ - public sealed class {actorInterfaceTypeName}ManualProxy : {fullyQualifiedActorInterfaceTypeName} + public sealed class {clientTypeName} : {fullyQualifiedActorInterfaceTypeName} {{ private readonly ActorProxy actorProxy; - public {actorInterfaceTypeName}ManualProxy(ActorProxy actorProxy) + public {clientTypeName}(ActorProxy actorProxy) {{ this.actorProxy = actorProxy; }} @@ -109,6 +121,15 @@ public void Initialize(GeneratorInitializationContext context) #endregion + private static string GetClientName(INamedTypeSymbol interfaceSymbol, AttributeData attributeData) + { + string? clientName = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "Name").Value.Value?.ToString(); + + clientName ??= $"{(interfaceSymbol.Name.StartsWith("I") ? interfaceSymbol.Name.Substring(1) : interfaceSymbol.Name)}Client"; + + return clientName; + } + private static string GenerateMethodImplementation(IMethodSymbol method) { var returnType = method.ReturnType as INamedTypeSymbol; From 037497c289c178baf52882dd0419322e4c45743f Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 5 Oct 2023 15:05:46 -0700 Subject: [PATCH 09/43] Support internal and non-actor interfaces. Signed-off-by: Phillip Hoff --- .../ActorClient/IMyPrivateActor.cs | 16 ++++++++++++++++ examples/GeneratedActor/ActorClient/Program.cs | 6 +++--- .../ActorCommon/IMyPublicActor.cs | 3 --- .../ActorClientGenerator.cs | 18 ++++++++++++++++-- 4 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 examples/GeneratedActor/ActorClient/IMyPrivateActor.cs diff --git a/examples/GeneratedActor/ActorClient/IMyPrivateActor.cs b/examples/GeneratedActor/ActorClient/IMyPrivateActor.cs new file mode 100644 index 000000000..1a5dd567a --- /dev/null +++ b/examples/GeneratedActor/ActorClient/IMyPrivateActor.cs @@ -0,0 +1,16 @@ +using Dapr.Actors.Generators; + +namespace GeneratedActor; + +internal sealed record MyPrivateState(string Name); + +/// +/// Non-remoted invocations have a strict limit on a single argument; CancellationToken is not supported. +/// +[GenerateActorClient] +internal interface IMyPrivateActor +{ + Task GetStateAsync(); + + Task SetStateAsync(MyPrivateState state); +} diff --git a/examples/GeneratedActor/ActorClient/Program.cs b/examples/GeneratedActor/ActorClient/Program.cs index d75b4c86e..a9e6722fb 100644 --- a/examples/GeneratedActor/ActorClient/Program.cs +++ b/examples/GeneratedActor/ActorClient/Program.cs @@ -82,13 +82,13 @@ async Task TestNonRemotedManualProxyAsync() async Task TestGeneratedProxyAsync() { - Console.WriteLine("Testing generated manual proxy..."); + Console.WriteLine("Testing generated proxy..."); var proxy = ActorProxy.Create(actorId, actorType /*, new ActorProxyOptions { UseJsonSerialization = true } */); - var client = new bar.IMyPublicActorManualProxy(proxy); + var client = new MyPrivateActorClient(proxy); var state = await client.GetStateAsync(); - await client.SetStateAsync(new MyState("Hello, World!")); + await client.SetStateAsync(new MyPrivateState("Hello, World!")); } diff --git a/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs b/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs index a4a730b3d..b96a239c2 100644 --- a/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs +++ b/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs @@ -1,6 +1,4 @@ -using System.Reflection.Metadata.Ecma335; using Dapr.Actors; -using Dapr.Actors.Generators; namespace GeneratedActor; @@ -9,7 +7,6 @@ public sealed record MyState(string Name); /// /// Non-remoted invocations have a strict limit on a single argument; CancellationToken is not supported. /// -[GenerateActorClient(Name = "MyBestActorClient")] public interface IMyPublicActor : IActor { Task GetStateAsync(); diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index cb4e77424..a7165acc5 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -16,7 +16,7 @@ public sealed class ActorClientGenerator : ISourceGenerator namespace Dapr.Actors.Generators { [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] - public sealed class GenerateActorClientAttribute : Attribute + internal sealed class GenerateActorClientAttribute : Attribute { public string? Name { get; set; } @@ -73,6 +73,7 @@ public void Execute(GeneratorExecutionContext context) var attributeData = interfaceSymbol.GetAttributes().Single(a => a.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) == true); + var accessibility = GetClientAccessibility(interfaceSymbol); var clientTypeName = GetClientName(interfaceSymbol, attributeData); var namespaceName = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "Namespace").Value.Value?.ToString() ?? interfaceSymbol.ContainingNamespace.ToDisplayString(); @@ -86,7 +87,7 @@ public void Execute(GeneratorExecutionContext context) namespace {namespaceName} {{ - public sealed class {clientTypeName} : {fullyQualifiedActorInterfaceTypeName} + {accessibility} sealed class {clientTypeName} : {fullyQualifiedActorInterfaceTypeName} {{ private readonly ActorProxy actorProxy; @@ -121,6 +122,19 @@ public void Initialize(GeneratorInitializationContext context) #endregion + private static string GetClientAccessibility(INamedTypeSymbol interfaceSymbol) + { + return interfaceSymbol.DeclaredAccessibility switch + { + Accessibility.Public => "public", + Accessibility.Internal => "internal", + Accessibility.Private => "private", + Accessibility.Protected => "protected", + Accessibility.ProtectedAndInternal => "protected internal", + _ => throw new InvalidOperationException("Unexpected accessibility.") + }; + } + private static string GetClientName(INamedTypeSymbol interfaceSymbol, AttributeData attributeData) { string? clientName = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "Name").Value.Value?.ToString(); From ac1c42df8a337593e46e9f872a5caaa8c987eb6a Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 5 Oct 2023 15:33:32 -0700 Subject: [PATCH 10/43] Enable mapping to "actual" method names. Signed-off-by: Phillip Hoff --- .../ActorClient/IMyPrivateActor.cs | 3 +- .../GeneratedActor/ActorClient/Program.cs | 2 +- .../ActorClientGenerator.cs | 68 ++++++++++++++----- 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/examples/GeneratedActor/ActorClient/IMyPrivateActor.cs b/examples/GeneratedActor/ActorClient/IMyPrivateActor.cs index 1a5dd567a..e4af061d4 100644 --- a/examples/GeneratedActor/ActorClient/IMyPrivateActor.cs +++ b/examples/GeneratedActor/ActorClient/IMyPrivateActor.cs @@ -10,7 +10,8 @@ internal sealed record MyPrivateState(string Name); [GenerateActorClient] internal interface IMyPrivateActor { - Task GetStateAsync(); + [ActorMethod(Name = "GetStateAsync")] + Task GetPrivateStateAsync(); Task SetStateAsync(MyPrivateState state); } diff --git a/examples/GeneratedActor/ActorClient/Program.cs b/examples/GeneratedActor/ActorClient/Program.cs index a9e6722fb..37f8fe253 100644 --- a/examples/GeneratedActor/ActorClient/Program.cs +++ b/examples/GeneratedActor/ActorClient/Program.cs @@ -88,7 +88,7 @@ async Task TestGeneratedProxyAsync() var client = new MyPrivateActorClient(proxy); - var state = await client.GetStateAsync(); + var state = await client.GetPrivateStateAsync(); await client.SetStateAsync(new MyPrivateState("Hello, World!")); } diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index a7165acc5..32fbc204c 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -10,19 +10,39 @@ namespace ActorGenerator; [Generator] public sealed class ActorClientGenerator : ISourceGenerator { - private const string AttributeText = @" + private const string GeneratorsNamespace = "Dapr.Actors.Generators"; + + private const string ActorMethodAttributeTypeName = "ActorMethodAttribute"; + private const string ActorMethodAttributeFullTypeName = GeneratorsNamespace + "." + ActorMethodAttributeTypeName; + + private const string GenerateActorClientAttribute = "GenerateActorClientAttribute"; + private const string GenerateActorClientAttributeFullTypeName = GeneratorsNamespace + "." + GenerateActorClientAttribute; + + private const string ActorMethodAttributeText = $@" #nullable enable - namespace Dapr.Actors.Generators - { + namespace {GeneratorsNamespace} + {{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + internal sealed class ActorMethodAttribute : Attribute + {{ + public string? Name {{ get; set; }} + }} + }}"; + + private const string GenerateActorClientAttributeText = $@" + #nullable enable + + namespace {GeneratorsNamespace} + {{ [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] internal sealed class GenerateActorClientAttribute : Attribute - { - public string? Name { get; set; } + {{ + public string? Name {{ get; set; }} - public string? Namespace { get; set; } - } - }"; + public string? Namespace {{ get; set; }} + }} + }}"; private sealed class ActorInterfaceSyntaxReceiver : ISyntaxContextReceiver { @@ -43,7 +63,7 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) var interfaceSymbol = context.SemanticModel.GetDeclaredSymbol(interfaceDeclarationSyntax) as INamedTypeSymbol; if (interfaceSymbol is null - || !interfaceSymbol.GetAttributes().Any(a => a.AttributeClass?.ToString() == "Dapr.Actors.Generators.GenerateActorClientAttribute")) + || !interfaceSymbol.GetAttributes().Any(a => a.AttributeClass?.ToString() == GenerateActorClientAttributeFullTypeName)) { return; } @@ -64,14 +84,15 @@ public void Execute(GeneratorExecutionContext context) return; } - var attributeSymbol = context.Compilation.GetTypeByMetadataName("Dapr.Actors.Generators.GenerateActorClientAttribute"); + var actorMethodAttributeSymbol = context.Compilation.GetTypeByMetadataName(ActorMethodAttributeFullTypeName) ?? throw new InvalidOperationException("Could not find ActorMethodAttribute."); + var generateActorClientAttributeSymbol = context.Compilation.GetTypeByMetadataName(GenerateActorClientAttributeFullTypeName) ?? throw new InvalidOperationException("Could not find GenerateActorClientAttribute."); foreach (var interfaceSymbol in actorInterfaceSyntaxReceiver.Models) { var actorInterfaceTypeName = interfaceSymbol.Name; var fullyQualifiedActorInterfaceTypeName = interfaceSymbol.ToString(); - var attributeData = interfaceSymbol.GetAttributes().Single(a => a.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) == true); + var attributeData = interfaceSymbol.GetAttributes().Single(a => a.AttributeClass?.Equals(generateActorClientAttributeSymbol, SymbolEqualityComparer.Default) == true); var accessibility = GetClientAccessibility(interfaceSymbol); var clientTypeName = GetClientName(interfaceSymbol, attributeData); @@ -79,7 +100,7 @@ public void Execute(GeneratorExecutionContext context) var members = interfaceSymbol.GetMembers().OfType().Where(m => m.MethodKind == MethodKind.Ordinary).ToList(); - var methodImplementations = String.Join("\n", members.Select(GenerateMethodImplementation)); + var methodImplementations = String.Join("\n", members.Select(member => GenerateMethodImplementation(member, actorMethodAttributeSymbol))); var source = $@"// using Dapr.Actors; @@ -115,7 +136,12 @@ public void Initialize(GeneratorInitializationContext context) } */ - context.RegisterForPostInitialization(i => i.AddSource("GenerateActorClientAttribute.g.cs", AttributeText)); + context.RegisterForPostInitialization( + i => + { + i.AddSource($"{ActorMethodAttributeFullTypeName}.g.cs", ActorMethodAttributeText); + i.AddSource($"{GenerateActorClientAttributeFullTypeName}.g.cs", GenerateActorClientAttributeText); + }); context.RegisterForSyntaxNotifications(() => new ActorInterfaceSyntaxReceiver()); } @@ -144,7 +170,7 @@ private static string GetClientName(INamedTypeSymbol interfaceSymbol, AttributeD return clientName; } - private static string GenerateMethodImplementation(IMethodSymbol method) + private static string GenerateMethodImplementation(IMethodSymbol method, INamedTypeSymbol generateActorClientAttributeSymbol) { var returnType = method.ReturnType as INamedTypeSymbol; @@ -159,8 +185,14 @@ private static string GenerateMethodImplementation(IMethodSymbol method) throw new InvalidOperationException("Too many parameters."); } + var attributeData = method.GetAttributes().SingleOrDefault(a => a.AttributeClass?.Equals(generateActorClientAttributeSymbol, SymbolEqualityComparer.Default) == true); + string methodName = method.Name; + string? actualMethodName = attributeData?.NamedArguments.SingleOrDefault(kvp => kvp.Key == "Name").Value.Value?.ToString(); + + actualMethodName ??= methodName; + var parameterType = method.Parameters.FirstOrDefault(); var returnTypeArgument = returnType.TypeArguments.FirstOrDefault(); @@ -170,7 +202,7 @@ private static string GenerateMethodImplementation(IMethodSymbol method) return $@" public {returnType} {methodName}({parameterType.Type} {parameterType.Name}) {{ - return this.actorProxy.InvokeMethodAsync<{parameterType.Type}, {returnTypeArgument}>(""{methodName}"", {parameterType.Name}); + return this.actorProxy.InvokeMethodAsync<{parameterType.Type}, {returnTypeArgument}>(""{actualMethodName}"", {parameterType.Name}); }} "; } @@ -179,7 +211,7 @@ private static string GenerateMethodImplementation(IMethodSymbol method) return $@" public {returnType} {methodName}() {{ - return this.actorProxy.InvokeMethodAsync(""{methodName}""); + return this.actorProxy.InvokeMethodAsync(""{actualMethodName}""); }} "; } @@ -188,7 +220,7 @@ private static string GenerateMethodImplementation(IMethodSymbol method) return $@" public {returnType} {methodName}({parameterType.Type} {parameterType.Name}) {{ - return this.actorProxy.InvokeMethodAsync(""{methodName}"", {parameterType.Name}); + return this.actorProxy.InvokeMethodAsync(""{actualMethodName}"", {parameterType.Name}); }} "; } @@ -197,7 +229,7 @@ private static string GenerateMethodImplementation(IMethodSymbol method) return $@" public {returnType} {methodName}() {{ - return this.actorProxy.InvokeMethodAsync<{returnTypeArgument}>(""{methodName}""); + return this.actorProxy.InvokeMethodAsync<{returnTypeArgument}>(""{actualMethodName}""); }} "; } From d6c63c500cd3aeab877e0a2efc6396ccb34c4a17 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Fri, 6 Oct 2023 10:47:59 -0700 Subject: [PATCH 11/43] Sketch initial generators unit test. Signed-off-by: Phillip Hoff --- all.sln | 7 ++ .../ActorClientGenerator.cs | 64 +++++------ .../ActorClientGeneratorTests.cs | 102 ++++++++++++++++++ .../CSharpSourceGeneratorVerifier.cs | 43 ++++++++ .../Dapr.Actors.Generators.Test.csproj | 35 ++++++ .../GlobalUsings.cs | 1 + 6 files changed, 221 insertions(+), 31 deletions(-) create mode 100644 test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs create mode 100644 test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs create mode 100644 test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj create mode 100644 test/Dapr.Actors.Generators.Test/GlobalUsings.cs diff --git a/all.sln b/all.sln index 081411ca5..6cfc56908 100644 --- a/all.sln +++ b/all.sln @@ -114,6 +114,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorGenerator", "examples\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorService", "examples\GeneratedActor\ActorService\ActorService.csproj", "{7C06FE2D-6C62-48F5-A505-F0D715C554DE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.Generators.Test", "test\Dapr.Actors.Generators.Test\Dapr.Actors.Generators.Test.csproj", "{AF89083D-4715-42E6-93E9-38497D12A8A6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -274,6 +276,10 @@ Global {7C06FE2D-6C62-48F5-A505-F0D715C554DE}.Debug|Any CPU.Build.0 = Debug|Any CPU {7C06FE2D-6C62-48F5-A505-F0D715C554DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {7C06FE2D-6C62-48F5-A505-F0D715C554DE}.Release|Any CPU.Build.0 = Release|Any CPU + {AF89083D-4715-42E6-93E9-38497D12A8A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF89083D-4715-42E6-93E9-38497D12A8A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF89083D-4715-42E6-93E9-38497D12A8A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF89083D-4715-42E6-93E9-38497D12A8A6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -324,6 +330,7 @@ Global {CB903D21-4869-42EF-BDD6-5B1CFF674337} = {7592AFA4-426B-42F3-AE82-957C86814482} {980B5FD8-0107-41F7-8FAD-E4E8BAE8A625} = {7592AFA4-426B-42F3-AE82-957C86814482} {7C06FE2D-6C62-48F5-A505-F0D715C554DE} = {7592AFA4-426B-42F3-AE82-957C86814482} + {AF89083D-4715-42E6-93E9-38497D12A8A6} = {DD020B34-460F-455F-8D17-CF4A949F100B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40} diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index 32fbc204c..343d8c325 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -2,7 +2,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace ActorGenerator; +namespace Dapr.Actors.Generators; /// /// Generates strongly-typed actor clients that use the non-remoting actor proxy. @@ -19,8 +19,12 @@ public sealed class ActorClientGenerator : ISourceGenerator private const string GenerateActorClientAttributeFullTypeName = GeneratorsNamespace + "." + GenerateActorClientAttribute; private const string ActorMethodAttributeText = $@" + // + #nullable enable + using System; + namespace {GeneratorsNamespace} {{ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] @@ -31,8 +35,12 @@ internal sealed class ActorMethodAttribute : Attribute }}"; private const string GenerateActorClientAttributeText = $@" + // + #nullable enable + using System; + namespace {GeneratorsNamespace} {{ [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] @@ -102,17 +110,16 @@ public void Execute(GeneratorExecutionContext context) var methodImplementations = String.Join("\n", members.Select(member => GenerateMethodImplementation(member, actorMethodAttributeSymbol))); - var source = $@"// -using Dapr.Actors; -using Dapr.Actors.Client; + var source = $@" +// namespace {namespaceName} {{ {accessibility} sealed class {clientTypeName} : {fullyQualifiedActorInterfaceTypeName} {{ - private readonly ActorProxy actorProxy; + private readonly Dapr.Actors.Client.ActorProxy actorProxy; - public {clientTypeName}(ActorProxy actorProxy) + public {clientTypeName}(Dapr.Actors.Client.ActorProxy actorProxy) {{ this.actorProxy = actorProxy; }} @@ -199,39 +206,34 @@ private static string GenerateMethodImplementation(IMethodSymbol method, INamedT if (returnTypeArgument is not null && parameterType is not null) { - return $@" - public {returnType} {methodName}({parameterType.Type} {parameterType.Name}) - {{ - return this.actorProxy.InvokeMethodAsync<{parameterType.Type}, {returnTypeArgument}>(""{actualMethodName}"", {parameterType.Name}); - }} - "; + return + $@"public {returnType} {methodName}({parameterType.Type} {parameterType.Name}) + {{ + return this.actorProxy.InvokeMethodAsync<{parameterType.Type}, {returnTypeArgument}>(""{actualMethodName}"", {parameterType.Name}); + }}"; } else if (returnTypeArgument is null && parameterType is null) { - return $@" - public {returnType} {methodName}() - {{ - return this.actorProxy.InvokeMethodAsync(""{actualMethodName}""); - }} - "; - } + return + $@"public {returnType} {methodName}() + {{ + return this.actorProxy.InvokeMethodAsync(""{actualMethodName}""); + }}";} else if (returnTypeArgument is null && parameterType is not null) { - return $@" - public {returnType} {methodName}({parameterType.Type} {parameterType.Name}) - {{ - return this.actorProxy.InvokeMethodAsync(""{actualMethodName}"", {parameterType.Name}); - }} - "; + return + $@"public {returnType} {methodName}({parameterType.Type} {parameterType.Name}) + {{ + return this.actorProxy.InvokeMethodAsync(""{actualMethodName}"", {parameterType.Name}); + }}"; } else if (returnTypeArgument is not null && parameterType is null) { - return $@" - public {returnType} {methodName}() - {{ - return this.actorProxy.InvokeMethodAsync<{returnTypeArgument}>(""{actualMethodName}""); - }} - "; + return + $@"public {returnType} {methodName}() + {{ + return this.actorProxy.InvokeMethodAsync<{returnTypeArgument}>(""{actualMethodName}""); + }}"; } else { diff --git a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs new file mode 100644 index 000000000..14db78af7 --- /dev/null +++ b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs @@ -0,0 +1,102 @@ +namespace Dapr.Actors.Generators; + +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; +using VerifyCS = CSharpSourceGeneratorVerifier; + +public sealed class ActorClientGeneratorTests +{ + private const string GeneratorsNamespace = "Dapr.Actors.Generators"; + + private const string ActorMethodAttributeText = $@" + // + + #nullable enable + + using System; + + namespace {GeneratorsNamespace} + {{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + internal sealed class ActorMethodAttribute : Attribute + {{ + public string? Name {{ get; set; }} + }} + }}"; + + private const string GenerateActorClientAttributeText = $@" + // + + #nullable enable + + using System; + + namespace {GeneratorsNamespace} + {{ + [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] + internal sealed class GenerateActorClientAttribute : Attribute + {{ + public string? Name {{ get; set; }} + + public string? Namespace {{ get; set; }} + }} + }}"; + + [Fact] + public async Task TestMethodWithNoArgumentsOrReturnValue() + { + var code = @" + using Dapr.Actors.Generators; + using System.Threading.Tasks; + + namespace Test + { + [GenerateActorClient] + public interface ITestActor + { + Task TestMethod(); + } + } + "; + + var expected = @" +// + +namespace Test +{ + public sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethod() + { + return this.actorProxy.InvokeMethodAsync(""TestMethod""); + } + } +} +"; + + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + TestState = + { + AdditionalReferences = { MetadataReference.CreateFromFile(typeof(Dapr.Actors.Client.ActorProxy).Assembly.Location) }, + Sources = { code }, + GeneratedSources = + { + ("Dapr.Actors.Generators/Dapr.Actors.Generators.ActorClientGenerator/Dapr.Actors.Generators.ActorMethodAttribute.g.cs", SourceText.From(ActorMethodAttributeText, Encoding.UTF8)), + ("Dapr.Actors.Generators/Dapr.Actors.Generators.ActorClientGenerator/Dapr.Actors.Generators.GenerateActorClientAttribute.g.cs", SourceText.From(GenerateActorClientAttributeText, Encoding.UTF8)), + ("Dapr.Actors.Generators/Dapr.Actors.Generators.ActorClientGenerator/ITestActor.g.cs", SourceText.From(expected, Encoding.UTF8)) + }, + }, + }.RunAsync(); + } +} \ No newline at end of file diff --git a/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs b/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs new file mode 100644 index 000000000..b0c9d6aee --- /dev/null +++ b/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs @@ -0,0 +1,43 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +/// +/// From Roslyn Source Generators Cookbook: https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md#unit-testing-of-generators +/// +internal static class CSharpSourceGeneratorVerifier + where TSourceGenerator : ISourceGenerator, new() +{ + public class Test : CSharpSourceGeneratorTest + { + public Test() + { + } + + protected override CompilationOptions CreateCompilationOptions() + { + var compilationOptions = base.CreateCompilationOptions(); + + return compilationOptions + .WithSpecificDiagnosticOptions(compilationOptions.SpecificDiagnosticOptions.SetItems(GetNullableWarningsFromCompiler())); + } + + public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.Default; + + private static ImmutableDictionary GetNullableWarningsFromCompiler() + { + string[] args = { "/warnaserror:nullable" }; + var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory); + var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions; + + return nullableWarnings; + } + + protected override ParseOptions CreateParseOptions() + { + return ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(LanguageVersion); + } + } +} \ No newline at end of file diff --git a/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj b/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj new file mode 100644 index 000000000..6ff0b5ebc --- /dev/null +++ b/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj @@ -0,0 +1,35 @@ + + + + net7.0 + 10.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/test/Dapr.Actors.Generators.Test/GlobalUsings.cs b/test/Dapr.Actors.Generators.Test/GlobalUsings.cs new file mode 100644 index 000000000..8c927eb74 --- /dev/null +++ b/test/Dapr.Actors.Generators.Test/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file From a8255b68ee9d8cf49cc82b2300bb0b7069c0798d Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Fri, 6 Oct 2023 14:39:06 -0700 Subject: [PATCH 12/43] Add second test. Signed-off-by: Phillip Hoff --- .../ActorClientGenerator.cs | 2 +- .../ActorClientGeneratorTests.cs | 114 +++++++++++++----- .../AdditionalMetadataReferences.cs | 8 ++ .../CSharpSourceGeneratorVerifier.cs | 2 + .../Dapr.Actors.Generators.Test.csproj | 2 +- 5 files changed, 93 insertions(+), 35 deletions(-) create mode 100644 test/Dapr.Actors.Generators.Test/AdditionalMetadataReferences.cs diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index 343d8c325..fe8e647aa 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -129,7 +129,7 @@ namespace {namespaceName} }} "; // Add the source code to the compilation - context.AddSource($"{actorInterfaceTypeName}.g.cs", source); + context.AddSource($"{namespaceName}.{clientTypeName}.g.cs", source); } } diff --git a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs index 14db78af7..a037a3a6c 100644 --- a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs +++ b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs @@ -1,15 +1,11 @@ namespace Dapr.Actors.Generators; using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; using VerifyCS = CSharpSourceGeneratorVerifier; public sealed class ActorClientGeneratorTests { - private const string GeneratorsNamespace = "Dapr.Actors.Generators"; - private const string ActorMethodAttributeText = $@" // @@ -17,7 +13,7 @@ public sealed class ActorClientGeneratorTests using System; - namespace {GeneratorsNamespace} + namespace Dapr.Actors.Generators {{ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] internal sealed class ActorMethodAttribute : Attribute @@ -26,6 +22,8 @@ internal sealed class ActorMethodAttribute : Attribute }} }}"; + private static readonly (string, SourceText) ActorMethodAttributeSource = ("Dapr.Actors.Generators/Dapr.Actors.Generators.ActorClientGenerator/Dapr.Actors.Generators.ActorMethodAttribute.g.cs", SourceText.From(ActorMethodAttributeText, Encoding.UTF8)); + private const string GenerateActorClientAttributeText = $@" // @@ -33,7 +31,7 @@ internal sealed class ActorMethodAttribute : Attribute using System; - namespace {GeneratorsNamespace} + namespace Dapr.Actors.Generators {{ [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] internal sealed class GenerateActorClientAttribute : Attribute @@ -44,24 +42,45 @@ internal sealed class GenerateActorClientAttribute : Attribute }} }}"; - [Fact] - public async Task TestMethodWithNoArgumentsOrReturnValue() + private static readonly (string, SourceText) GenerateActorClientAttributeSource = ("Dapr.Actors.Generators/Dapr.Actors.Generators.ActorClientGenerator/Dapr.Actors.Generators.GenerateActorClientAttribute.g.cs", SourceText.From(GenerateActorClientAttributeText, Encoding.UTF8)); + + private static VerifyCS.Test CreateTest(string originalSource, string generatedName, string generatedSource) { - var code = @" - using Dapr.Actors.Generators; - using System.Threading.Tasks; + return new VerifyCS.Test + { - namespace Test + TestState = { - [GenerateActorClient] - public interface ITestActor + AdditionalReferences = { AdditionalMetadataReferences.Actors }, + Sources = { originalSource }, + GeneratedSources = { - Task TestMethod(); - } + ActorMethodAttributeSource, + GenerateActorClientAttributeSource, + ($"Dapr.Actors.Generators/Dapr.Actors.Generators.ActorClientGenerator/{generatedName}", SourceText.From(generatedSource, Encoding.UTF8)) + }, } - "; + }; + } - var expected = @" + [Fact] + public async Task TestMethodWithNoArgumentsOrReturnValue() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + [GenerateActorClient] + public interface ITestActor + { + Task TestMethod(); + } +} +"; + + var generatedSource = @" // namespace Test @@ -83,20 +102,49 @@ public System.Threading.Tasks.Task TestMethod() } "; - await new VerifyCS.Test + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } + + [Fact] + public async Task TestMethodWithArgumentsButNoReturnValue() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + public record TestValue(int Value); + + [GenerateActorClient] + public interface ITestActor + { + Task TestMethod(TestValue value); + } +} +"; + + var generatedSource = @" +// + +namespace Test +{ + public sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) { - ReferenceAssemblies = ReferenceAssemblies.Net.Net60, - TestState = - { - AdditionalReferences = { MetadataReference.CreateFromFile(typeof(Dapr.Actors.Client.ActorProxy).Assembly.Location) }, - Sources = { code }, - GeneratedSources = - { - ("Dapr.Actors.Generators/Dapr.Actors.Generators.ActorClientGenerator/Dapr.Actors.Generators.ActorMethodAttribute.g.cs", SourceText.From(ActorMethodAttributeText, Encoding.UTF8)), - ("Dapr.Actors.Generators/Dapr.Actors.Generators.ActorClientGenerator/Dapr.Actors.Generators.GenerateActorClientAttribute.g.cs", SourceText.From(GenerateActorClientAttributeText, Encoding.UTF8)), - ("Dapr.Actors.Generators/Dapr.Actors.Generators.ActorClientGenerator/ITestActor.g.cs", SourceText.From(expected, Encoding.UTF8)) - }, - }, - }.RunAsync(); + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethod(Test.TestValue value) + { + return this.actorProxy.InvokeMethodAsync(""TestMethod"", value); + } } -} \ No newline at end of file +} +"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + }} \ No newline at end of file diff --git a/test/Dapr.Actors.Generators.Test/AdditionalMetadataReferences.cs b/test/Dapr.Actors.Generators.Test/AdditionalMetadataReferences.cs new file mode 100644 index 000000000..9c2de7404 --- /dev/null +++ b/test/Dapr.Actors.Generators.Test/AdditionalMetadataReferences.cs @@ -0,0 +1,8 @@ +using Microsoft.CodeAnalysis; + +namespace Dapr.Actors.Generators; + +internal static class AdditionalMetadataReferences +{ + public static readonly MetadataReference Actors = MetadataReference.CreateFromFile(typeof(Dapr.Actors.Client.ActorProxy).Assembly.Location); +} \ No newline at end of file diff --git a/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs b/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs index b0c9d6aee..a7d8c267d 100644 --- a/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs +++ b/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs @@ -14,6 +14,8 @@ public class Test : CSharpSourceGeneratorTest { public Test() { + // NOTE: There's no Net70 yet, so we're using Net60 for now. Hopefully it "just works". + this.ReferenceAssemblies = Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net.Net60; } protected override CompilationOptions CreateCompilationOptions() diff --git a/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj b/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj index 6ff0b5ebc..e4be63f57 100644 --- a/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj +++ b/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj @@ -1,7 +1,7 @@ - net7.0 + net6;net7 10.0 enable enable From 1aff78f19d48ccc7a826294b7e56b01488491b50 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Fri, 6 Oct 2023 14:49:58 -0700 Subject: [PATCH 13/43] Complete basic four args/return cases. Signed-off-by: Phillip Hoff --- .../ActorClientGeneratorTests.cs | 93 ++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs index a037a3a6c..fc27691d9 100644 --- a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs +++ b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs @@ -147,4 +147,95 @@ public System.Threading.Tasks.Task TestMethod(Test.TestValue value) "; await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); - }} \ No newline at end of file + } + + [Fact] + public async Task TestMethodWithNoArgumentsButReturnValue() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + public record TestValue(int Value); + + [GenerateActorClient] + public interface ITestActor + { + Task TestMethodAsync(); + } +} +"; + + var generatedSource = @" +// + +namespace Test +{ + public sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethodAsync() + { + return this.actorProxy.InvokeMethodAsync(""TestMethodAsync""); + } + } +} +"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } + + [Fact] + public async Task TestMethodWithArgumentsAndReturnValue() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + public record TestRequestValue(int Value); + + public record TestReturnValue(int Value); + + [GenerateActorClient] + public interface ITestActor + { + Task TestMethodAsync(TestRequestValue value); + } +} +"; + + var generatedSource = @" +// + +namespace Test +{ + public sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethodAsync(Test.TestRequestValue value) + { + return this.actorProxy.InvokeMethodAsync(""TestMethodAsync"", value); + } + } +} +"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } +} \ No newline at end of file From d7fd160583db01835b62e426cb5a0a4937d65ac2 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Tue, 10 Oct 2023 08:59:36 -0700 Subject: [PATCH 14/43] Sketch test for cancellation tokens. Signed-off-by: Phillip Hoff --- all.sln | 2 +- .../ActorClientGenerator.cs | 77 ++++++++++++---- .../GenerateActorClientAttribute.cs | 9 -- .../ActorClientGeneratorTests.cs | 88 +++++++++++++++++++ 4 files changed, 151 insertions(+), 25 deletions(-) delete mode 100644 src/Dapr.Actors.Generators/GenerateActorClientAttribute.cs diff --git a/all.sln b/all.sln index 6cfc56908..fa75b50d9 100644 --- a/all.sln +++ b/all.sln @@ -110,7 +110,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorClient", "examples\Gen EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorCommon", "examples\GeneratedActor\ActorCommon\ActorCommon.csproj", "{CB903D21-4869-42EF-BDD6-5B1CFF674337}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorGenerator", "examples\GeneratedActor\ActorGenerator\ActorGenerator.csproj", "{980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.Generators", "src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj", "{980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorService", "examples\GeneratedActor\ActorService\ActorService.csproj", "{7C06FE2D-6C62-48F5-A505-F0D715C554DE}" EndProject diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index fe8e647aa..542fb22bf 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -94,6 +94,7 @@ public void Execute(GeneratorExecutionContext context) var actorMethodAttributeSymbol = context.Compilation.GetTypeByMetadataName(ActorMethodAttributeFullTypeName) ?? throw new InvalidOperationException("Could not find ActorMethodAttribute."); var generateActorClientAttributeSymbol = context.Compilation.GetTypeByMetadataName(GenerateActorClientAttributeFullTypeName) ?? throw new InvalidOperationException("Could not find GenerateActorClientAttribute."); + var cancellationTokenSymbol = context.Compilation.GetTypeByMetadataName("System.Threading.CancellationToken") ?? throw new InvalidOperationException("Could not find CancellationToken."); foreach (var interfaceSymbol in actorInterfaceSyntaxReceiver.Models) { @@ -108,7 +109,7 @@ public void Execute(GeneratorExecutionContext context) var members = interfaceSymbol.GetMembers().OfType().Where(m => m.MethodKind == MethodKind.Ordinary).ToList(); - var methodImplementations = String.Join("\n", members.Select(member => GenerateMethodImplementation(member, actorMethodAttributeSymbol))); + var methodImplementations = String.Join("\n", members.Select(member => GenerateMethodImplementation(member, actorMethodAttributeSymbol, cancellationTokenSymbol))); var source = $@" // @@ -177,7 +178,7 @@ private static string GetClientName(INamedTypeSymbol interfaceSymbol, AttributeD return clientName; } - private static string GenerateMethodImplementation(IMethodSymbol method, INamedTypeSymbol generateActorClientAttributeSymbol) + private static string GenerateMethodImplementation(IMethodSymbol method, INamedTypeSymbol generateActorClientAttributeSymbol, INamedTypeSymbol cancellationTokenSymbol) { var returnType = method.ReturnType as INamedTypeSymbol; @@ -187,9 +188,21 @@ private static string GenerateMethodImplementation(IMethodSymbol method, INamedT throw new InvalidOperationException("Return type is not a named type symbol."); } - if (method.Parameters.Length > 1) + int cancellationTokenIndex = method.Parameters.IndexOf(p => p.Type.Equals(cancellationTokenSymbol, SymbolEqualityComparer.Default)); + + if (cancellationTokenIndex != -1 && cancellationTokenIndex != method.Parameters.Length - 1) + { + throw new InvalidOperationException("Cancellation tokens must be the last argument."); + } + + if (method.Parameters.Length > 1 && cancellationTokenIndex == -1) + { + throw new InvalidOperationException("Only methods with a single argument or a single argument followed by a cancellation token are supported."); + } + + if (method.Parameters.Length > 2) { - throw new InvalidOperationException("Too many parameters."); + throw new InvalidOperationException("Only methods with a single argument or a single argument followed by a cancellation token are supported."); } var attributeData = method.GetAttributes().SingleOrDefault(a => a.AttributeClass?.Equals(generateActorClientAttributeSymbol, SymbolEqualityComparer.Default) == true); @@ -200,39 +213,43 @@ private static string GenerateMethodImplementation(IMethodSymbol method, INamedT actualMethodName ??= methodName; - var parameterType = method.Parameters.FirstOrDefault(); + var cancellationTokenParameter = cancellationTokenIndex != -1 ? method.Parameters[cancellationTokenIndex] : null; + + var firstParameter = method.Parameters.FirstOrDefault(); + + var parameterType = firstParameter is not null && !SymbolEqualityComparer.Default.Equals(firstParameter, cancellationTokenParameter) ? firstParameter : null; var returnTypeArgument = returnType.TypeArguments.FirstOrDefault(); if (returnTypeArgument is not null && parameterType is not null) { return - $@"public {returnType} {methodName}({parameterType.Type} {parameterType.Name}) + $@"public {returnType} {methodName}({CreateArgumentDefinitions(method.Parameters)}) {{ - return this.actorProxy.InvokeMethodAsync<{parameterType.Type}, {returnTypeArgument}>(""{actualMethodName}"", {parameterType.Name}); + return this.actorProxy.InvokeMethodAsync<{parameterType.Type}, {returnTypeArgument}>({CreateArgumentList(actualMethodName, method.Parameters)}); }}"; } else if (returnTypeArgument is null && parameterType is null) { return - $@"public {returnType} {methodName}() + $@"public {returnType} {methodName}({CreateArgumentDefinitions(method.Parameters)}) {{ - return this.actorProxy.InvokeMethodAsync(""{actualMethodName}""); + return this.actorProxy.InvokeMethodAsync({CreateArgumentList(actualMethodName, method.Parameters)}); }}";} else if (returnTypeArgument is null && parameterType is not null) { return - $@"public {returnType} {methodName}({parameterType.Type} {parameterType.Name}) + $@"public {returnType} {methodName}({CreateArgumentDefinitions(method.Parameters)}) {{ - return this.actorProxy.InvokeMethodAsync(""{actualMethodName}"", {parameterType.Name}); + return this.actorProxy.InvokeMethodAsync({CreateArgumentList(actualMethodName, method.Parameters)}); }}"; } else if (returnTypeArgument is not null && parameterType is null) { return - $@"public {returnType} {methodName}() + $@"public {returnType} {methodName}({CreateArgumentDefinitions(method.Parameters)}) {{ - return this.actorProxy.InvokeMethodAsync<{returnTypeArgument}>(""{actualMethodName}""); + return this.actorProxy.InvokeMethodAsync<{returnTypeArgument}>({CreateArgumentList(actualMethodName, method.Parameters)}); }}"; } else @@ -240,4 +257,34 @@ private static string GenerateMethodImplementation(IMethodSymbol method, INamedT throw new InvalidOperationException("Unexpected case."); } } -} \ No newline at end of file + + private static string CreateArgumentDefinitions(IEnumerable parameters) + { + return String.Join(", ", parameters.Select(p => $"{p.Type} {p.Name}")); + } + + private static string CreateArgumentList(string methodName, IEnumerable parameters) + { + return String.Join(", ", new[] { $@"""{methodName}""" }.Concat(parameters.Select(p => p.Name))); + } +} + +internal static class Extensions +{ + public static int IndexOf(this IEnumerable source, Func predicate) + { + int index = 0; + + foreach (var item in source) + { + if (predicate(item)) + { + return index; + } + + index++; + } + + return -1; + } +} diff --git a/src/Dapr.Actors.Generators/GenerateActorClientAttribute.cs b/src/Dapr.Actors.Generators/GenerateActorClientAttribute.cs deleted file mode 100644 index f231b5f8f..000000000 --- a/src/Dapr.Actors.Generators/GenerateActorClientAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Dapr.Actors; - -/// -/// Indicates an actor interface should have a strongly-typed client generated for it. -/// -[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] -public sealed class GenerateActorClientAttribute : Attribute -{ -} diff --git a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs index fc27691d9..de1ef9203 100644 --- a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs +++ b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs @@ -234,6 +234,94 @@ public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) } } } +"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } + + [Fact] + public async Task TestMethodWithCancellationTokenArgument() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading; +using System.Threading.Tasks; + +namespace Test +{ + [GenerateActorClient] + public interface ITestActor + { + Task TestMethodAsync(CancellationToken cancellationToken = default); + } +} +"; + + var generatedSource = @" +// + +namespace Test +{ + public sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethodAsync(System.Threading.CancellationToken cancellationToken) + { + return this.actorProxy.InvokeMethodAsync(""TestMethodAsync"", cancellationToken); + } + } +} +"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } + + [Fact] + public async Task TestMethodWithValueAndCancellationTokenArguments() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading; +using System.Threading.Tasks; + +namespace Test +{ + public record TestValue(int Value); + + [GenerateActorClient] + public interface ITestActor + { + Task TestMethodAsync(TestValue value, CancellationToken cancellationToken = default); + } +} +"; + + var generatedSource = @" +// + +namespace Test +{ + public sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethodAsync(Test.TestValue value, System.Threading.CancellationToken cancellationToken) + { + return this.actorProxy.InvokeMethodAsync(""TestMethodAsync"", value, cancellationToken); + } + } +} "; await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); From 9a3d4c376dde7de47216ad3e8d775a557f0d2f6a Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Tue, 10 Oct 2023 10:23:20 -0700 Subject: [PATCH 15/43] Simplify method generation scenarios. Signed-off-by: Phillip Hoff --- .../ActorClientGenerator.cs | 72 ++++--------------- 1 file changed, 12 insertions(+), 60 deletions(-) diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index 542fb22bf..3572d8917 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -180,14 +180,6 @@ private static string GetClientName(INamedTypeSymbol interfaceSymbol, AttributeD private static string GenerateMethodImplementation(IMethodSymbol method, INamedTypeSymbol generateActorClientAttributeSymbol, INamedTypeSymbol cancellationTokenSymbol) { - var returnType = method.ReturnType as INamedTypeSymbol; - - if (returnType is null) - { - // TODO: Return a diagnostic instead. - throw new InvalidOperationException("Return type is not a named type symbol."); - } - int cancellationTokenIndex = method.Parameters.IndexOf(p => p.Type.Equals(cancellationTokenSymbol, SymbolEqualityComparer.Default)); if (cancellationTokenIndex != -1 && cancellationTokenIndex != method.Parameters.Length - 1) @@ -207,65 +199,25 @@ private static string GenerateMethodImplementation(IMethodSymbol method, INamedT var attributeData = method.GetAttributes().SingleOrDefault(a => a.AttributeClass?.Equals(generateActorClientAttributeSymbol, SymbolEqualityComparer.Default) == true); - string methodName = method.Name; + string? actualMethodName = attributeData?.NamedArguments.SingleOrDefault(kvp => kvp.Key == "Name").Value.Value?.ToString() ?? method.Name; - string? actualMethodName = attributeData?.NamedArguments.SingleOrDefault(kvp => kvp.Key == "Name").Value.Value?.ToString(); - - actualMethodName ??= methodName; - - var cancellationTokenParameter = cancellationTokenIndex != -1 ? method.Parameters[cancellationTokenIndex] : null; + var requestParameter = method.Parameters.Length > 0 && cancellationTokenIndex != 0 ? method.Parameters[0] : null; - var firstParameter = method.Parameters.FirstOrDefault(); + var returnTypeArgument = (method.ReturnType as INamedTypeSymbol)?.TypeArguments.FirstOrDefault(); - var parameterType = firstParameter is not null && !SymbolEqualityComparer.Default.Equals(firstParameter, cancellationTokenParameter) ? firstParameter : null; + string argumentDefinitions = String.Join(", ", method.Parameters.Select(p => $"{p.Type} {p.Name}")); + string argumentList = String.Join(", ", new[] { $@"""{actualMethodName}""" }.Concat(method.Parameters.Select(p => p.Name))); - var returnTypeArgument = returnType.TypeArguments.FirstOrDefault(); + string templateArgs = + returnTypeArgument is not null + ? $"<{(requestParameter is not null ? $"{requestParameter.Type}, " : "")}{returnTypeArgument}>" + : ""; - if (returnTypeArgument is not null && parameterType is not null) - { - return - $@"public {returnType} {methodName}({CreateArgumentDefinitions(method.Parameters)}) - {{ - return this.actorProxy.InvokeMethodAsync<{parameterType.Type}, {returnTypeArgument}>({CreateArgumentList(actualMethodName, method.Parameters)}); - }}"; - } - else if (returnTypeArgument is null && parameterType is null) - { - return - $@"public {returnType} {methodName}({CreateArgumentDefinitions(method.Parameters)}) - {{ - return this.actorProxy.InvokeMethodAsync({CreateArgumentList(actualMethodName, method.Parameters)}); - }}";} - else if (returnTypeArgument is null && parameterType is not null) - { - return - $@"public {returnType} {methodName}({CreateArgumentDefinitions(method.Parameters)}) + return + $@"public {method.ReturnType} {method.Name}({argumentDefinitions}) {{ - return this.actorProxy.InvokeMethodAsync({CreateArgumentList(actualMethodName, method.Parameters)}); + return this.actorProxy.InvokeMethodAsync{templateArgs}({argumentList}); }}"; - } - else if (returnTypeArgument is not null && parameterType is null) - { - return - $@"public {returnType} {methodName}({CreateArgumentDefinitions(method.Parameters)}) - {{ - return this.actorProxy.InvokeMethodAsync<{returnTypeArgument}>({CreateArgumentList(actualMethodName, method.Parameters)}); - }}"; - } - else - { - throw new InvalidOperationException("Unexpected case."); - } - } - - private static string CreateArgumentDefinitions(IEnumerable parameters) - { - return String.Join(", ", parameters.Select(p => $"{p.Type} {p.Name}")); - } - - private static string CreateArgumentList(string methodName, IEnumerable parameters) - { - return String.Join(", ", new[] { $@"""{methodName}""" }.Concat(parameters.Select(p => p.Name))); } } From 639bd6d990b7f9960e303a3b12da5c9ed462a057 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Tue, 10 Oct 2023 11:02:20 -0700 Subject: [PATCH 16/43] Sketch return of diagnostics. Signed-off-by: Phillip Hoff --- .../ActorClientGenerator.cs | 64 ++++++++++----- .../ActorClientGeneratorTests.cs | 77 +++++++++++++++++-- 2 files changed, 118 insertions(+), 23 deletions(-) diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index 3572d8917..ea5d452d8 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -98,20 +98,22 @@ public void Execute(GeneratorExecutionContext context) foreach (var interfaceSymbol in actorInterfaceSyntaxReceiver.Models) { - var actorInterfaceTypeName = interfaceSymbol.Name; - var fullyQualifiedActorInterfaceTypeName = interfaceSymbol.ToString(); + try + { + var actorInterfaceTypeName = interfaceSymbol.Name; + var fullyQualifiedActorInterfaceTypeName = interfaceSymbol.ToString(); - var attributeData = interfaceSymbol.GetAttributes().Single(a => a.AttributeClass?.Equals(generateActorClientAttributeSymbol, SymbolEqualityComparer.Default) == true); + var attributeData = interfaceSymbol.GetAttributes().Single(a => a.AttributeClass?.Equals(generateActorClientAttributeSymbol, SymbolEqualityComparer.Default) == true); - var accessibility = GetClientAccessibility(interfaceSymbol); - var clientTypeName = GetClientName(interfaceSymbol, attributeData); - var namespaceName = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "Namespace").Value.Value?.ToString() ?? interfaceSymbol.ContainingNamespace.ToDisplayString(); + var accessibility = GetClientAccessibility(interfaceSymbol); + var clientTypeName = GetClientName(interfaceSymbol, attributeData); + var namespaceName = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "Namespace").Value.Value?.ToString() ?? interfaceSymbol.ContainingNamespace.ToDisplayString(); - var members = interfaceSymbol.GetMembers().OfType().Where(m => m.MethodKind == MethodKind.Ordinary).ToList(); + var members = interfaceSymbol.GetMembers().OfType().Where(m => m.MethodKind == MethodKind.Ordinary).ToList(); - var methodImplementations = String.Join("\n", members.Select(member => GenerateMethodImplementation(member, actorMethodAttributeSymbol, cancellationTokenSymbol))); + var methodImplementations = String.Join("\n", members.Select(member => GenerateMethodImplementation(member, actorMethodAttributeSymbol, cancellationTokenSymbol))); - var source = $@" + var source = $@" // namespace {namespaceName} @@ -129,8 +131,16 @@ namespace {namespaceName} }} }} "; - // Add the source code to the compilation - context.AddSource($"{namespaceName}.{clientTypeName}.g.cs", source); + // Add the source code to the compilation + context.AddSource($"{namespaceName}.{clientTypeName}.g.cs", source); + } + catch (DiagnosticsException e) + { + foreach (var diagnostic in e.Diagnostics) + { + context.ReportDiagnostic(diagnostic); + } + } } } @@ -187,14 +197,21 @@ private static string GenerateMethodImplementation(IMethodSymbol method, INamedT throw new InvalidOperationException("Cancellation tokens must be the last argument."); } - if (method.Parameters.Length > 1 && cancellationTokenIndex == -1) + if ((method.Parameters.Length > 1 && cancellationTokenIndex == -1) + || (method.Parameters.Length > 2)) { - throw new InvalidOperationException("Only methods with a single argument or a single argument followed by a cancellation token are supported."); - } - - if (method.Parameters.Length > 2) - { - throw new InvalidOperationException("Only methods with a single argument or a single argument followed by a cancellation token are supported."); + throw new DiagnosticsException(new[] + { + Diagnostic.Create( + new DiagnosticDescriptor( + "DAPR0001", + "Invalid method signature.", + "Only methods with a single argument or a single argument followed by a cancellation token are supported.", + "Dapr.Actors.Generators", + DiagnosticSeverity.Error, + true), + method.Locations.First()) + }); } var attributeData = method.GetAttributes().SingleOrDefault(a => a.AttributeClass?.Equals(generateActorClientAttributeSymbol, SymbolEqualityComparer.Default) == true); @@ -240,3 +257,14 @@ public static int IndexOf(this IEnumerable source, Func predicate return -1; } } + +internal sealed class DiagnosticsException : Exception +{ + public DiagnosticsException(IEnumerable diagnostics) + : base(String.Join("\n", diagnostics.Select(d => d.ToString()))) + { + this.Diagnostics = diagnostics.ToArray(); + } + + public IEnumerable Diagnostics { get; } +} diff --git a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs index de1ef9203..197221959 100644 --- a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs +++ b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs @@ -1,6 +1,8 @@ namespace Dapr.Actors.Generators; using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; using VerifyCS = CSharpSourceGeneratorVerifier; @@ -44,11 +46,10 @@ internal sealed class GenerateActorClientAttribute : Attribute private static readonly (string, SourceText) GenerateActorClientAttributeSource = ("Dapr.Actors.Generators/Dapr.Actors.Generators.ActorClientGenerator/Dapr.Actors.Generators.GenerateActorClientAttribute.g.cs", SourceText.From(GenerateActorClientAttributeText, Encoding.UTF8)); - private static VerifyCS.Test CreateTest(string originalSource, string generatedName, string generatedSource) + private static VerifyCS.Test CreateTest(string originalSource, string? generatedName = null, string? generatedSource = null) { - return new VerifyCS.Test + var test = new VerifyCS.Test { - TestState = { AdditionalReferences = { AdditionalMetadataReferences.Actors }, @@ -56,11 +57,17 @@ private static VerifyCS.Test CreateTest(string originalSource, string generatedN GeneratedSources = { ActorMethodAttributeSource, - GenerateActorClientAttributeSource, - ($"Dapr.Actors.Generators/Dapr.Actors.Generators.ActorClientGenerator/{generatedName}", SourceText.From(generatedSource, Encoding.UTF8)) + GenerateActorClientAttributeSource }, } }; + + if (generatedName is not null && generatedSource is not null) + { + test.TestState.GeneratedSources.Add(($"Dapr.Actors.Generators/Dapr.Actors.Generators.ActorClientGenerator/{generatedName}", SourceText.From(generatedSource, Encoding.UTF8))); + } + + return test; } [Fact] @@ -326,4 +333,64 @@ public System.Threading.Tasks.Task TestMethodAsync(Test.TestValue value, System. await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); } + + [Fact] + public async Task TestMethodWithTooManyArguments() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading; +using System.Threading.Tasks; + +namespace Test +{ + public record TestValue(int Value); + + [GenerateActorClient] + public interface ITestActor + { + Task TestMethodAsync(int value1, int value2); + } +} +"; + + var test = CreateTest(originalSource); + + test.TestState.ExpectedDiagnostics.Add( + new DiagnosticResult("DAPR0001", DiagnosticSeverity.Error) + .WithSpan(13, 14, 13, 29) + .WithMessage("Only methods with a single argument or a single argument followed by a cancellation token are supported.")); + + await test.RunAsync(); + } + + [Fact] + public async Task TestMethodWithFarTooManyArguments() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading; +using System.Threading.Tasks; + +namespace Test +{ + public record TestValue(int Value); + + [GenerateActorClient] + public interface ITestActor + { + Task TestMethodAsync(int value1, int value2, CancellationToken cancellationToken); + } +} +"; + + var test = CreateTest(originalSource); + + test.TestState.ExpectedDiagnostics.Add( + new DiagnosticResult("DAPR0001", DiagnosticSeverity.Error) + .WithSpan(13, 14, 13, 29) + .WithMessage("Only methods with a single argument or a single argument followed by a cancellation token are supported.")); + + await test.RunAsync(); + } } \ No newline at end of file From a14a0269134fc0c0df18a7f5859b7ae54a5616aa Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Tue, 10 Oct 2023 11:07:09 -0700 Subject: [PATCH 17/43] Validate cancellation token ordering. Signed-off-by: Phillip Hoff --- .../ActorClientGenerator.cs | 15 ++++++-- .../ActorClientGeneratorTests.cs | 34 +++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index ea5d452d8..a92d0656a 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -194,7 +194,18 @@ private static string GenerateMethodImplementation(IMethodSymbol method, INamedT if (cancellationTokenIndex != -1 && cancellationTokenIndex != method.Parameters.Length - 1) { - throw new InvalidOperationException("Cancellation tokens must be the last argument."); + throw new DiagnosticsException(new[] + { + Diagnostic.Create( + new DiagnosticDescriptor( + "DAPR0001", + "Invalid method signature.", + "Cancellation tokens must be the last argument.", + "Dapr.Actors.Generators", + DiagnosticSeverity.Error, + true), + method.Parameters[cancellationTokenIndex].Locations.First()) + }); } if ((method.Parameters.Length > 1 && cancellationTokenIndex == -1) @@ -204,7 +215,7 @@ private static string GenerateMethodImplementation(IMethodSymbol method, INamedT { Diagnostic.Create( new DiagnosticDescriptor( - "DAPR0001", + "DAPR0002", "Invalid method signature.", "Only methods with a single argument or a single argument followed by a cancellation token are supported.", "Dapr.Actors.Generators", diff --git a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs index 197221959..cf9b71ba2 100644 --- a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs +++ b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs @@ -334,6 +334,36 @@ public System.Threading.Tasks.Task TestMethodAsync(Test.TestValue value, System. await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); } + [Fact] + public async Task TestMethodWithReversedArguments() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading; +using System.Threading.Tasks; + +namespace Test +{ + public record TestValue(int Value); + + [GenerateActorClient] + public interface ITestActor + { + Task TestMethodAsync(CancellationToken cancellationToken, int value); + } +} +"; + + var test = CreateTest(originalSource); + + test.TestState.ExpectedDiagnostics.Add( + new DiagnosticResult("DAPR0001", DiagnosticSeverity.Error) + .WithSpan(13, 48, 13, 65) + .WithMessage("Cancellation tokens must be the last argument.")); + + await test.RunAsync(); + } + [Fact] public async Task TestMethodWithTooManyArguments() { @@ -357,7 +387,7 @@ public interface ITestActor var test = CreateTest(originalSource); test.TestState.ExpectedDiagnostics.Add( - new DiagnosticResult("DAPR0001", DiagnosticSeverity.Error) + new DiagnosticResult("DAPR0002", DiagnosticSeverity.Error) .WithSpan(13, 14, 13, 29) .WithMessage("Only methods with a single argument or a single argument followed by a cancellation token are supported.")); @@ -387,7 +417,7 @@ public interface ITestActor var test = CreateTest(originalSource); test.TestState.ExpectedDiagnostics.Add( - new DiagnosticResult("DAPR0001", DiagnosticSeverity.Error) + new DiagnosticResult("DAPR0002", DiagnosticSeverity.Error) .WithSpan(13, 14, 13, 29) .WithMessage("Only methods with a single argument or a single argument followed by a cancellation token are supported.")); From c931282aa321017adfc360da660101a22793c271 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Tue, 10 Oct 2023 12:45:47 -0700 Subject: [PATCH 18/43] Suppport default cancellation tokens. Signed-off-by: Phillip Hoff --- .../ActorClientGenerator.cs | 14 ++- .../ActorClientGeneratorTests.cs | 92 ++++++++++++++++++- 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index a92d0656a..1a2ee9582 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -191,8 +191,9 @@ private static string GetClientName(INamedTypeSymbol interfaceSymbol, AttributeD private static string GenerateMethodImplementation(IMethodSymbol method, INamedTypeSymbol generateActorClientAttributeSymbol, INamedTypeSymbol cancellationTokenSymbol) { int cancellationTokenIndex = method.Parameters.IndexOf(p => p.Type.Equals(cancellationTokenSymbol, SymbolEqualityComparer.Default)); + var cancellationTokenParameter = cancellationTokenIndex != -1 ? method.Parameters[cancellationTokenIndex] : null; - if (cancellationTokenIndex != -1 && cancellationTokenIndex != method.Parameters.Length - 1) + if (cancellationTokenParameter is not null && cancellationTokenIndex != method.Parameters.Length - 1) { throw new DiagnosticsException(new[] { @@ -204,7 +205,7 @@ private static string GenerateMethodImplementation(IMethodSymbol method, INamedT "Dapr.Actors.Generators", DiagnosticSeverity.Error, true), - method.Parameters[cancellationTokenIndex].Locations.First()) + cancellationTokenParameter.Locations.First()) }); } @@ -234,6 +235,15 @@ private static string GenerateMethodImplementation(IMethodSymbol method, INamedT var returnTypeArgument = (method.ReturnType as INamedTypeSymbol)?.TypeArguments.FirstOrDefault(); string argumentDefinitions = String.Join(", ", method.Parameters.Select(p => $"{p.Type} {p.Name}")); + + if (cancellationTokenParameter is not null + && cancellationTokenParameter.IsOptional + && cancellationTokenParameter.HasExplicitDefaultValue + && cancellationTokenParameter.ExplicitDefaultValue is null) + { + argumentDefinitions = argumentDefinitions + " = default"; + } + string argumentList = String.Join(", ", new[] { $@"""{actualMethodName}""" }.Concat(method.Parameters.Select(p => p.Name))); string templateArgs = diff --git a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs index cf9b71ba2..7ac8f315d 100644 --- a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs +++ b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs @@ -259,7 +259,7 @@ namespace Test [GenerateActorClient] public interface ITestActor { - Task TestMethodAsync(CancellationToken cancellationToken = default); + Task TestMethodAsync(CancellationToken cancellationToken); } } "; @@ -289,6 +289,49 @@ public System.Threading.Tasks.Task TestMethodAsync(System.Threading.Cancellation await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); } + [Fact] + public async Task TestMethodWithDefaultCancellationTokenArgument() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading; +using System.Threading.Tasks; + +namespace Test +{ + [GenerateActorClient] + public interface ITestActor + { + Task TestMethodAsync(CancellationToken cancellationToken = default); + } +} +"; + + var generatedSource = @" +// + +namespace Test +{ + public sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethodAsync(System.Threading.CancellationToken cancellationToken = default) + { + return this.actorProxy.InvokeMethodAsync(""TestMethodAsync"", cancellationToken); + } + } +} +"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } + [Fact] public async Task TestMethodWithValueAndCancellationTokenArguments() { @@ -304,7 +347,7 @@ public record TestValue(int Value); [GenerateActorClient] public interface ITestActor { - Task TestMethodAsync(TestValue value, CancellationToken cancellationToken = default); + Task TestMethodAsync(TestValue value, CancellationToken cancellationToken); } } "; @@ -334,6 +377,51 @@ public System.Threading.Tasks.Task TestMethodAsync(Test.TestValue value, System. await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); } + [Fact] + public async Task TestMethodWithValueAndDefaultCancellationTokenArguments() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading; +using System.Threading.Tasks; + +namespace Test +{ + public record TestValue(int Value); + + [GenerateActorClient] + public interface ITestActor + { + Task TestMethodAsync(TestValue value, CancellationToken cancellationToken = default); + } +} +"; + + var generatedSource = @" +// + +namespace Test +{ + public sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethodAsync(Test.TestValue value, System.Threading.CancellationToken cancellationToken = default) + { + return this.actorProxy.InvokeMethodAsync(""TestMethodAsync"", value, cancellationToken); + } + } +} +"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } + [Fact] public async Task TestMethodWithReversedArguments() { From 91a1410792ba233ee33057798997f3be1a1ed382 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Tue, 10 Oct 2023 13:14:58 -0700 Subject: [PATCH 19/43] Test attribute variations. Signed-off-by: Phillip Hoff --- .../ActorClientGeneratorTests.cs | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs index 7ac8f315d..4a7f7d4b9 100644 --- a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs +++ b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs @@ -112,6 +112,175 @@ public System.Threading.Tasks.Task TestMethod() await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); } + [Fact] + public async Task TestInternalInterface() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + [GenerateActorClient] + internal interface ITestActor + { + Task TestMethod(); + } +} +"; + + var generatedSource = @" +// + +namespace Test +{ + internal sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethod() + { + return this.actorProxy.InvokeMethodAsync(""TestMethod""); + } + } +} +"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } + + [Fact] + public async Task TestRenamedClient() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + [GenerateActorClient(Name = ""MyTestActorClient"")] + internal interface ITestActor + { + Task TestMethod(); + } +} +"; + + var generatedSource = @" +// + +namespace Test +{ + internal sealed class MyTestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + + public MyTestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethod() + { + return this.actorProxy.InvokeMethodAsync(""TestMethod""); + } + } +} +"; + + await CreateTest(originalSource, "Test.MyTestActorClient.g.cs", generatedSource).RunAsync(); + } + + [Fact] + public async Task TestCustomNamespace() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + [GenerateActorClient(Namespace = ""MyTest"")] + internal interface ITestActor + { + Task TestMethod(); + } +} +"; + + var generatedSource = @" +// + +namespace MyTest +{ + internal sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethod() + { + return this.actorProxy.InvokeMethodAsync(""TestMethod""); + } + } +} +"; + + await CreateTest(originalSource, "MyTest.TestActorClient.g.cs", generatedSource).RunAsync(); + } + + [Fact] + public async Task TestRenamedMethod() + { + var originalSource = @" +using Dapr.Actors.Generators; +using System.Threading.Tasks; + +namespace Test +{ + [GenerateActorClient] + public interface ITestActor + { + [ActorMethod(Name = ""MyTestMethod"")] + Task TestMethod(); + } +} +"; + + var generatedSource = @" +// + +namespace Test +{ + public sealed class TestActorClient : Test.ITestActor + { + private readonly Dapr.Actors.Client.ActorProxy actorProxy; + + public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy) + { + this.actorProxy = actorProxy; + } + + public System.Threading.Tasks.Task TestMethod() + { + return this.actorProxy.InvokeMethodAsync(""MyTestMethod""); + } + } +} +"; + + await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync(); + } + [Fact] public async Task TestMethodWithArgumentsButNoReturnValue() { From 90140c223c273142606d5988be6feae45a315473 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Tue, 10 Oct 2023 13:37:55 -0700 Subject: [PATCH 20/43] Scaffold generators E2E. Signed-off-by: Phillip Hoff --- all.sln | 9 ++++++- .../Dapr.E2E.Test.Actors.Generators.csproj | 26 +++++++++++++++++++ .../GlobalUsings.cs | 1 + .../UnitTest1.cs | 10 +++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj create mode 100644 test/Dapr.E2E.Test.Actors.Generators/GlobalUsings.cs create mode 100644 test/Dapr.E2E.Test.Actors.Generators/UnitTest1.cs diff --git a/all.sln b/all.sln index fa75b50d9..9d62a6c86 100644 --- a/all.sln +++ b/all.sln @@ -110,12 +110,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorClient", "examples\Gen EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorCommon", "examples\GeneratedActor\ActorCommon\ActorCommon.csproj", "{CB903D21-4869-42EF-BDD6-5B1CFF674337}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.Generators", "src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj", "{980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.Generators", "src\Dapr.Actors.Generators\Dapr.Actors.Generators.csproj", "{980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorService", "examples\GeneratedActor\ActorService\ActorService.csproj", "{7C06FE2D-6C62-48F5-A505-F0D715C554DE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.Generators.Test", "test\Dapr.Actors.Generators.Test\Dapr.Actors.Generators.Test.csproj", "{AF89083D-4715-42E6-93E9-38497D12A8A6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.E2E.Test.Actors.Generators", "test\Dapr.E2E.Test.Actors.Generators\Dapr.E2E.Test.Actors.Generators.csproj", "{B5CDB0DC-B26D-48F1-B934-FE5C1C991940}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -280,6 +282,10 @@ Global {AF89083D-4715-42E6-93E9-38497D12A8A6}.Debug|Any CPU.Build.0 = Debug|Any CPU {AF89083D-4715-42E6-93E9-38497D12A8A6}.Release|Any CPU.ActiveCfg = Release|Any CPU {AF89083D-4715-42E6-93E9-38497D12A8A6}.Release|Any CPU.Build.0 = Release|Any CPU + {B5CDB0DC-B26D-48F1-B934-FE5C1C991940}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5CDB0DC-B26D-48F1-B934-FE5C1C991940}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5CDB0DC-B26D-48F1-B934-FE5C1C991940}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5CDB0DC-B26D-48F1-B934-FE5C1C991940}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -331,6 +337,7 @@ Global {980B5FD8-0107-41F7-8FAD-E4E8BAE8A625} = {7592AFA4-426B-42F3-AE82-957C86814482} {7C06FE2D-6C62-48F5-A505-F0D715C554DE} = {7592AFA4-426B-42F3-AE82-957C86814482} {AF89083D-4715-42E6-93E9-38497D12A8A6} = {DD020B34-460F-455F-8D17-CF4A949F100B} + {B5CDB0DC-B26D-48F1-B934-FE5C1C991940} = {DD020B34-460F-455F-8D17-CF4A949F100B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40} diff --git a/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj b/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj new file mode 100644 index 000000000..cdee8fc2a --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj @@ -0,0 +1,26 @@ + + + + net6;net7 + 10.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/test/Dapr.E2E.Test.Actors.Generators/GlobalUsings.cs b/test/Dapr.E2E.Test.Actors.Generators/GlobalUsings.cs new file mode 100644 index 000000000..8c927eb74 --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/test/Dapr.E2E.Test.Actors.Generators/UnitTest1.cs b/test/Dapr.E2E.Test.Actors.Generators/UnitTest1.cs new file mode 100644 index 000000000..b728db358 --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace Dapr.E2E.Test.Actors.Generators; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +} \ No newline at end of file From b30fd9add50af401b38d3512a23a6bb06c2553a7 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Wed, 11 Oct 2023 09:22:38 -0700 Subject: [PATCH 21/43] Sketch start of E2E test. Signed-off-by: Phillip Hoff --- .../ActorWebApplicationFactory.cs | 26 +++++ .../Dapr.E2E.Test.Actors.Generators.csproj | 7 +- .../DaprSidecarFactory.cs | 102 ++++++++++++++++++ .../GeneratedClientTests.cs | 81 ++++++++++++++ .../IRemoteActor.cs | 12 +++ .../PortManager.cs | 33 ++++++ .../RemoteActor.cs | 32 ++++++ .../UnitTest1.cs | 10 -- .../XUnitLoggingProvider.cs | 63 +++++++++++ 9 files changed, 355 insertions(+), 11 deletions(-) create mode 100644 test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs create mode 100644 test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs create mode 100644 test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs create mode 100644 test/Dapr.E2E.Test.Actors.Generators/IRemoteActor.cs create mode 100644 test/Dapr.E2E.Test.Actors.Generators/PortManager.cs create mode 100644 test/Dapr.E2E.Test.Actors.Generators/RemoteActor.cs delete mode 100644 test/Dapr.E2E.Test.Actors.Generators/UnitTest1.cs create mode 100644 test/Dapr.E2E.Test.Actors.Generators/XUnitLoggingProvider.cs diff --git a/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs b/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs new file mode 100644 index 000000000..2d321a2f2 --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs @@ -0,0 +1,26 @@ +using Dapr.Actors.Runtime; + +namespace Dapr.E2E.Test.Actors.Generators; + +public sealed class ActorWebApplicationFactory +{ + public static WebApplication Create(Action configure) + { + var builder = WebApplication.CreateBuilder(); + + builder.Services.AddActors(configure); + + var app = builder.Build(); + + app.UseRouting(); + + #pragma warning disable ASP0014 + app.UseEndpoints( + endpoints => + { + endpoints.MapActorsHandlers(); + }); + + return app; + } +} diff --git a/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj b/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj index cdee8fc2a..bc928d575 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj +++ b/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj @@ -1,4 +1,4 @@ - + net6;net7 @@ -11,6 +11,7 @@ + @@ -23,4 +24,8 @@ + + + + diff --git a/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs b/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs new file mode 100644 index 000000000..d410f392c --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs @@ -0,0 +1,102 @@ +using System.Diagnostics; + +namespace Dapr.E2E.Test.Actors.Generators; + +internal sealed record DaprSidecarOptions(string AppId) +{ + public int? AppPort { get; init; } + + public int? DaprGrpcPort { get; init;} + + public int? DaprHttpPort { get; init; } + + public ILoggerFactory? LoggerFactory { get; init; } +} + +internal sealed class DaprSidecar : IAsyncDisposable +{ + private readonly Process process; + private readonly ILogger? logger; + + public DaprSidecar(DaprSidecarOptions options) + { + string arguments = $"run --app-id {options.AppId}"; + + if (options.DaprGrpcPort is not null) + { + arguments += $" --dapr-grpc-port {options.DaprGrpcPort}"; + } + + if (options.DaprHttpPort is not null) + { + arguments += $" --dapr-http-port {options.DaprHttpPort}"; + } + + this.process = new Process + { + EnableRaisingEvents = false, // ? + StartInfo = + { + Arguments = arguments, + CreateNoWindow = true, + FileName = "dapr", + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden + } + }; + + if (options.LoggerFactory is not null) + { + this.logger = options.LoggerFactory.CreateLogger(options.AppId); + + this.process.StartInfo.RedirectStandardError = true; + this.process.StartInfo.RedirectStandardOutput = true; + + this.process.OutputDataReceived += (_, args) => + { + if (args.Data is not null) + { + this.logger.LogInformation(args.Data); + } + }; + + this.process.ErrorDataReceived += (_, args) => + { + if (args.Data is not null) + { + this.logger.LogError(args.Data); + } + }; + } + } + + public Task StartAsync(CancellationToken cancellationToken = default) + { + process.Start(); + + process.BeginErrorReadLine(); + process.BeginOutputReadLine(); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken = default) + { + process.Kill(entireProcessTree: true); + + return process.WaitForExitAsync(cancellationToken); + } + + public async ValueTask DisposeAsync() + { + await StopAsync(CancellationToken.None); + } +} + +internal sealed class DaprSidecarFactory +{ + public static DaprSidecar Create(DaprSidecarOptions options) + { + return new(options); + } +} \ No newline at end of file diff --git a/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs b/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs new file mode 100644 index 000000000..34bdd4a2b --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs @@ -0,0 +1,81 @@ +using Microsoft.VisualStudio.TestPlatform.TestHost; +using Xunit.Abstractions; + +namespace Dapr.E2E.Test.Actors.Generators; + +public class GeneratedClientTests +{ + private readonly ITestOutputHelper testOutputHelper; + + public GeneratedClientTests(ITestOutputHelper testOutputHelper) + { + this.testOutputHelper = testOutputHelper; + } + + [Fact] + public async Task TestGeneratedClientAsync() + { + var portManager = new PortManager(); + + var reservedPorts = portManager.ReservePorts(5).ToArray(); + + var appPort = reservedPorts[0]; + + var serviceAppGrpcPort = reservedPorts[1]; + var serviceAppHttpPort = reservedPorts[2]; + + var clientAppGrpcPort = reservedPorts[3]; + var clientAppHttpPort = reservedPorts[4]; + + var loggerFactory = new LoggerFactory(); + + loggerFactory.AddProvider(new XUnitLoggingProvider(this.testOutputHelper)); + + var serviceAppSidecarOptions = new DaprSidecarOptions("service-app") + { + AppPort = appPort, + DaprGrpcPort = serviceAppGrpcPort, + DaprHttpPort = serviceAppHttpPort, + LoggerFactory = loggerFactory + }; + + var clientAppSidecarOptions = new DaprSidecarOptions("client-app") + { + DaprGrpcPort = clientAppGrpcPort, + DaprHttpPort = clientAppHttpPort, + LoggerFactory = loggerFactory + }; + + await using var app = ActorWebApplicationFactory.Create( + options => + { + options.UseJsonSerialization = true; + + // TODO: Register actors dynamically. + options.Actors.RegisterActor(); + }); + + using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + + app.Urls.Add($"http://localhost:{appPort}"); + app.Configuration["DAPR_GRPC_PORT"] = serviceAppGrpcPort.ToString(); + app.Configuration["DAPR_HTTP_PORT"] = serviceAppHttpPort.ToString(); + + await app.StartAsync(cancellationTokenSource.Token); + + // TODO: Start the service app sidecar + + await using var serviceAppSidecar = DaprSidecarFactory.Create(serviceAppSidecarOptions); + + await serviceAppSidecar.StartAsync(cancellationTokenSource.Token); + + // TODO: Start the client app sidecar + await using var clientAppSidecar = DaprSidecarFactory.Create(clientAppSidecarOptions); + + await clientAppSidecar.StartAsync(cancellationTokenSource.Token); + + await Task.Delay(TimeSpan.FromSeconds(15), cancellationTokenSource.Token); + + // TODO: Start the client + } +} diff --git a/test/Dapr.E2E.Test.Actors.Generators/IRemoteActor.cs b/test/Dapr.E2E.Test.Actors.Generators/IRemoteActor.cs new file mode 100644 index 000000000..ab0efde17 --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/IRemoteActor.cs @@ -0,0 +1,12 @@ +using Dapr.Actors; + +namespace Dapr.E2E.Test.Actors.Generators; + +public record RemoteState(string Value); + +public interface IRemoteActor : IActor +{ + Task GetState(); + + Task SetState(RemoteState state); +} \ No newline at end of file diff --git a/test/Dapr.E2E.Test.Actors.Generators/PortManager.cs b/test/Dapr.E2E.Test.Actors.Generators/PortManager.cs new file mode 100644 index 000000000..341e46210 --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/PortManager.cs @@ -0,0 +1,33 @@ +using System.Net.NetworkInformation; + +namespace Dapr.E2E.Test.Actors.Generators; + +internal sealed class PortManager +{ + private readonly ISet reservedPorts = new HashSet(); + + private readonly object reservationLock = new(); + + public ISet ReservePorts(int count, int rangeStart = 55000) + { + lock (this.reservationLock) + { + var globalProperties = IPGlobalProperties.GetIPGlobalProperties(); + + var activePorts = + globalProperties + .GetActiveTcpListeners() + .Select(endPoint => endPoint.Port) + .ToHashSet(); + + var availablePorts = + Enumerable + .Range(rangeStart, Int32.MaxValue - rangeStart + 1) + .Where(port => !activePorts.Contains(port)) + .Where(port => !this.reservedPorts.Contains(port)) + .Take(count); + + return availablePorts.ToHashSet(); + } + } +} \ No newline at end of file diff --git a/test/Dapr.E2E.Test.Actors.Generators/RemoteActor.cs b/test/Dapr.E2E.Test.Actors.Generators/RemoteActor.cs new file mode 100644 index 000000000..098de7374 --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/RemoteActor.cs @@ -0,0 +1,32 @@ +using Dapr.Actors.Runtime; + +namespace Dapr.E2E.Test.Actors.Generators; + +internal sealed class RemoteActor : Actor, IRemoteActor +{ + private readonly ILogger logger; + + private RemoteState currentState = new("default"); + + public RemoteActor(ActorHost host, ILogger logger) + : base(host) + { + this.logger = logger; + } + + public Task GetState() + { + this.logger.LogInformation("GetStateAsync called."); + + return Task.FromResult(this.currentState); + } + + public Task SetState(RemoteState state) + { + this.logger.LogInformation("SetStateAsync called."); + + this.currentState = state; + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/test/Dapr.E2E.Test.Actors.Generators/UnitTest1.cs b/test/Dapr.E2E.Test.Actors.Generators/UnitTest1.cs deleted file mode 100644 index b728db358..000000000 --- a/test/Dapr.E2E.Test.Actors.Generators/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Dapr.E2E.Test.Actors.Generators; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - - } -} \ No newline at end of file diff --git a/test/Dapr.E2E.Test.Actors.Generators/XUnitLoggingProvider.cs b/test/Dapr.E2E.Test.Actors.Generators/XUnitLoggingProvider.cs new file mode 100644 index 000000000..0625d1b14 --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/XUnitLoggingProvider.cs @@ -0,0 +1,63 @@ +using Xunit.Abstractions; + +namespace Dapr.E2E.Test.Actors.Generators; + +internal sealed class XUnitLoggingProvider : ILoggerProvider +{ + private readonly ITestOutputHelper output; + + public XUnitLoggingProvider(ITestOutputHelper output) + { + this.output = output; + } + + public ILogger CreateLogger(string categoryName) + { + return new XUnitLogger(categoryName, this.output); + } + + public void Dispose() + { + } + + private sealed class XUnitLogger : ILogger + { + private readonly string categoryName; + private readonly ITestOutputHelper output; + + public XUnitLogger(string categoryName, ITestOutputHelper output) + { + this.categoryName = categoryName; + this.output = output; + } + +#nullable disable + public IDisposable BeginScope(TState state) + { + return new XUnitLoggerScope(); + } +#nullable enable + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log( + LogLevel logLevel, + EventId eventId, + TState state, + Exception? exception, + Func formatter) + { + this.output.WriteLine($"{this.categoryName}: {formatter(state, exception).TrimEnd(Environment.NewLine.ToCharArray())}"); + } + } + + private sealed class XUnitLoggerScope : IDisposable + { + public void Dispose() + { + } + } +} \ No newline at end of file From 507771f0c1e962b00fa2f580bfdad4de86cd93b0 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Wed, 11 Oct 2023 09:59:55 -0700 Subject: [PATCH 22/43] Add wait for Dapr sidecar start. Signed-off-by: Phillip Hoff --- .../ActorWebApplicationFactory.cs | 13 +++++-- .../DaprSidecarFactory.cs | 38 +++++++++++-------- .../GeneratedClientTests.cs | 14 +++++-- 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs b/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs index 2d321a2f2..3a1eed53d 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs @@ -2,13 +2,20 @@ namespace Dapr.E2E.Test.Actors.Generators; -public sealed class ActorWebApplicationFactory +internal sealed record ActorWebApplicationOptions(Action ConfigureActors) { - public static WebApplication Create(Action configure) + public Action? ConfigureBuilder { get; init; } +} + +internal sealed class ActorWebApplicationFactory +{ + public static WebApplication Create(ActorWebApplicationOptions options) { var builder = WebApplication.CreateBuilder(); - builder.Services.AddActors(configure); + options.ConfigureBuilder?.Invoke(builder); + + builder.Services.AddActors(options.ConfigureActors); var app = builder.Build(); diff --git a/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs b/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs index d410f392c..d3907b8c9 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs @@ -15,8 +15,11 @@ internal sealed record DaprSidecarOptions(string AppId) internal sealed class DaprSidecar : IAsyncDisposable { + private const string StartupOutputString = "dapr initialized. Status: Running."; + private readonly Process process; private readonly ILogger? logger; + private readonly TaskCompletionSource tcs = new(); public DaprSidecar(DaprSidecarOptions options) { @@ -40,6 +43,8 @@ public DaprSidecar(DaprSidecarOptions options) Arguments = arguments, CreateNoWindow = true, FileName = "dapr", + RedirectStandardError = true, + RedirectStandardOutput = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden } @@ -48,26 +53,28 @@ public DaprSidecar(DaprSidecarOptions options) if (options.LoggerFactory is not null) { this.logger = options.LoggerFactory.CreateLogger(options.AppId); + } - this.process.StartInfo.RedirectStandardError = true; - this.process.StartInfo.RedirectStandardOutput = true; - - this.process.OutputDataReceived += (_, args) => + this.process.OutputDataReceived += (_, args) => + { + if (args.Data is not null) { - if (args.Data is not null) + if (args.Data.Contains(StartupOutputString)) { - this.logger.LogInformation(args.Data); + this.tcs.SetResult(true); } - }; - this.process.ErrorDataReceived += (_, args) => + this.logger?.LogInformation(args.Data); + } + }; + + this.process.ErrorDataReceived += (_, args) => + { + if (args.Data is not null) { - if (args.Data is not null) - { - this.logger.LogError(args.Data); - } - }; - } + this.logger?.LogError(args.Data); + } + }; } public Task StartAsync(CancellationToken cancellationToken = default) @@ -77,11 +84,12 @@ public Task StartAsync(CancellationToken cancellationToken = default) process.BeginErrorReadLine(); process.BeginOutputReadLine(); - return Task.CompletedTask; + return this.tcs.Task; } public Task StopAsync(CancellationToken cancellationToken = default) { + // TODO: Shutdown more cleanly (e.g. `dapr stop`). process.Kill(entireProcessTree: true); return process.WaitForExitAsync(cancellationToken); diff --git a/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs b/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs index 34bdd4a2b..4db84856d 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs @@ -27,9 +27,10 @@ public async Task TestGeneratedClientAsync() var clientAppGrpcPort = reservedPorts[3]; var clientAppHttpPort = reservedPorts[4]; + var loggerProvider = new XUnitLoggingProvider(this.testOutputHelper); var loggerFactory = new LoggerFactory(); - loggerFactory.AddProvider(new XUnitLoggingProvider(this.testOutputHelper)); + loggerFactory.AddProvider(loggerProvider); var serviceAppSidecarOptions = new DaprSidecarOptions("service-app") { @@ -47,12 +48,17 @@ public async Task TestGeneratedClientAsync() }; await using var app = ActorWebApplicationFactory.Create( - options => + new ActorWebApplicationOptions(options => { options.UseJsonSerialization = true; - - // TODO: Register actors dynamically. options.Actors.RegisterActor(); + }) + { + ConfigureBuilder = builder => + { + builder.Logging.ClearProviders(); + builder.Logging.AddProvider(loggerProvider); + } }); using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); From 1a3b64f4e1ebfe968447494f021aa5c0a41611aa Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Wed, 11 Oct 2023 11:08:16 -0700 Subject: [PATCH 23/43] Sketch client test code. Signed-off-by: Phillip Hoff --- .../Dapr.E2E.Test.Actors.Generators.csproj | 3 +++ .../DaprSidecarFactory.cs | 14 ++++++++++++- .../GeneratedClientTests.cs | 20 ++++++++++++++++--- .../IClientActor.cs | 15 ++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 test/Dapr.E2E.Test.Actors.Generators/IClientActor.cs diff --git a/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj b/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj index bc928d575..8c44ec87b 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj +++ b/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj @@ -26,6 +26,9 @@ + diff --git a/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs b/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs index d3907b8c9..b712fe027 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs @@ -11,11 +11,13 @@ internal sealed record DaprSidecarOptions(string AppId) public int? DaprHttpPort { get; init; } public ILoggerFactory? LoggerFactory { get; init; } + + public string? LogLevel { get; init; } } internal sealed class DaprSidecar : IAsyncDisposable { - private const string StartupOutputString = "dapr initialized. Status: Running."; + private const string StartupOutputString = "You're up and running! Dapr logs will appear here."; private readonly Process process; private readonly ILogger? logger; @@ -25,6 +27,11 @@ public DaprSidecar(DaprSidecarOptions options) { string arguments = $"run --app-id {options.AppId}"; + if (options.AppPort is not null) + { + arguments += $" --app-port {options.AppPort}"; + } + if (options.DaprGrpcPort is not null) { arguments += $" --dapr-grpc-port {options.DaprGrpcPort}"; @@ -35,6 +42,11 @@ public DaprSidecar(DaprSidecarOptions options) arguments += $" --dapr-http-port {options.DaprHttpPort}"; } + if (options.LogLevel is not null) + { + arguments += $" --log-level {options.LogLevel}"; + } + this.process = new Process { EnableRaisingEvents = false, // ? diff --git a/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs b/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs index 4db84856d..7b4992967 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs @@ -1,3 +1,5 @@ +using Dapr.Actors; +using Dapr.Actors.Client; using Microsoft.VisualStudio.TestPlatform.TestHost; using Xunit.Abstractions; @@ -37,7 +39,8 @@ public async Task TestGeneratedClientAsync() AppPort = appPort, DaprGrpcPort = serviceAppGrpcPort, DaprHttpPort = serviceAppHttpPort, - LoggerFactory = loggerFactory + LoggerFactory = loggerFactory, + LogLevel = "debug" }; var clientAppSidecarOptions = new DaprSidecarOptions("client-app") @@ -80,8 +83,19 @@ public async Task TestGeneratedClientAsync() await clientAppSidecar.StartAsync(cancellationTokenSource.Token); - await Task.Delay(TimeSpan.FromSeconds(15), cancellationTokenSource.Token); - // TODO: Start the client + var actorId = new ActorId("test-actor"); + + var actorProxy = ActorProxy.Create(actorId, "RemoteActor", + new ActorProxyOptions + { + HttpEndpoint = $"http://localhost:{clientAppHttpPort}", + }); + + var client = new ClientActorClient(actorProxy); + + var result = await client.GetStateAsync(cancellationTokenSource.Token); + + await client.SetStateAsync(new ClientState("updated state"), cancellationTokenSource.Token); } } diff --git a/test/Dapr.E2E.Test.Actors.Generators/IClientActor.cs b/test/Dapr.E2E.Test.Actors.Generators/IClientActor.cs new file mode 100644 index 000000000..fefe38f9e --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/IClientActor.cs @@ -0,0 +1,15 @@ +using Dapr.Actors.Generators; + +namespace Dapr.E2E.Test.Actors.Generators; + +internal record ClientState(string Value); + +[GenerateActorClient] +internal interface IClientActor +{ + [ActorMethod(Name = "GetState")] + Task GetStateAsync(CancellationToken cancellationToken = default); + + [ActorMethod(Name = "SetState")] + Task SetStateAsync(ClientState state, CancellationToken cancellationToken = default); +} From 9309d6a86e61ec6f2a67317dc6f1a5653eccb255 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Wed, 11 Oct 2023 11:58:37 -0700 Subject: [PATCH 24/43] Add wait for actor readiness. Signed-off-by: Phillip Hoff --- .../DaprSidecarFactory.cs | 24 +++++++++-- .../GeneratedClientTests.cs | 43 +++++++++---------- .../IRemoteActor.cs | 2 +- .../IRemotePingActor.cs | 8 ++++ .../RemoteActor.cs | 5 +++ 5 files changed, 55 insertions(+), 27 deletions(-) create mode 100644 test/Dapr.E2E.Test.Actors.Generators/IRemotePingActor.cs diff --git a/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs b/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs index b712fe027..d759799aa 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs @@ -19,6 +19,7 @@ internal sealed class DaprSidecar : IAsyncDisposable { private const string StartupOutputString = "You're up and running! Dapr logs will appear here."; + private readonly string appId; private readonly Process process; private readonly ILogger? logger; private readonly TaskCompletionSource tcs = new(); @@ -87,6 +88,8 @@ public DaprSidecar(DaprSidecarOptions options) this.logger?.LogError(args.Data); } }; + + this.appId = options.AppId; } public Task StartAsync(CancellationToken cancellationToken = default) @@ -99,12 +102,25 @@ public Task StartAsync(CancellationToken cancellationToken = default) return this.tcs.Task; } - public Task StopAsync(CancellationToken cancellationToken = default) + public async Task StopAsync(CancellationToken cancellationToken = default) { - // TODO: Shutdown more cleanly (e.g. `dapr stop`). - process.Kill(entireProcessTree: true); + var stopProcess = new Process + { + StartInfo = + { + Arguments = $"stop --app-id {this.appId}", + CreateNoWindow = true, + FileName = "dapr", + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden + } + }; + + stopProcess.Start(); + + await stopProcess.WaitForExitAsync(cancellationToken); - return process.WaitForExitAsync(cancellationToken); + await process.WaitForExitAsync(cancellationToken); } public async ValueTask DisposeAsync() diff --git a/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs b/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs index 7b4992967..95a2ef33c 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs @@ -1,6 +1,5 @@ using Dapr.Actors; using Dapr.Actors.Client; -using Microsoft.VisualStudio.TestPlatform.TestHost; using Xunit.Abstractions; namespace Dapr.E2E.Test.Actors.Generators; @@ -19,15 +18,10 @@ public async Task TestGeneratedClientAsync() { var portManager = new PortManager(); - var reservedPorts = portManager.ReservePorts(5).ToArray(); + var reservedPorts = portManager.ReservePorts(2).ToArray(); var appPort = reservedPorts[0]; - - var serviceAppGrpcPort = reservedPorts[1]; - var serviceAppHttpPort = reservedPorts[2]; - - var clientAppGrpcPort = reservedPorts[3]; - var clientAppHttpPort = reservedPorts[4]; + var clientAppHttpPort = reservedPorts[1]; var loggerProvider = new XUnitLoggingProvider(this.testOutputHelper); var loggerFactory = new LoggerFactory(); @@ -37,15 +31,12 @@ public async Task TestGeneratedClientAsync() var serviceAppSidecarOptions = new DaprSidecarOptions("service-app") { AppPort = appPort, - DaprGrpcPort = serviceAppGrpcPort, - DaprHttpPort = serviceAppHttpPort, LoggerFactory = loggerFactory, LogLevel = "debug" }; var clientAppSidecarOptions = new DaprSidecarOptions("client-app") { - DaprGrpcPort = clientAppGrpcPort, DaprHttpPort = clientAppHttpPort, LoggerFactory = loggerFactory }; @@ -67,30 +58,38 @@ public async Task TestGeneratedClientAsync() using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); app.Urls.Add($"http://localhost:{appPort}"); - app.Configuration["DAPR_GRPC_PORT"] = serviceAppGrpcPort.ToString(); - app.Configuration["DAPR_HTTP_PORT"] = serviceAppHttpPort.ToString(); await app.StartAsync(cancellationTokenSource.Token); - // TODO: Start the service app sidecar - await using var serviceAppSidecar = DaprSidecarFactory.Create(serviceAppSidecarOptions); await serviceAppSidecar.StartAsync(cancellationTokenSource.Token); - // TODO: Start the client app sidecar await using var clientAppSidecar = DaprSidecarFactory.Create(clientAppSidecarOptions); await clientAppSidecar.StartAsync(cancellationTokenSource.Token); - // TODO: Start the client - var actorId = new ActorId("test-actor"); + var actorId = ActorId.CreateRandom(); + var actorType = "RemoteActor"; + var actorOptions = new ActorProxyOptions { HttpEndpoint = $"http://localhost:{clientAppHttpPort}" }; - var actorProxy = ActorProxy.Create(actorId, "RemoteActor", - new ActorProxyOptions + var pingProxy = ActorProxy.Create(actorId, actorType, actorOptions); + + while (true) + { + try { - HttpEndpoint = $"http://localhost:{clientAppHttpPort}", - }); + await pingProxy.Ping(); + + break; + } + catch (DaprApiException) + { + await Task.Delay(TimeSpan.FromMilliseconds(250), cancellationTokenSource.Token); + } + } + + var actorProxy = ActorProxy.Create(actorId, actorType, actorOptions); var client = new ClientActorClient(actorProxy); diff --git a/test/Dapr.E2E.Test.Actors.Generators/IRemoteActor.cs b/test/Dapr.E2E.Test.Actors.Generators/IRemoteActor.cs index ab0efde17..44e31ba4e 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/IRemoteActor.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/IRemoteActor.cs @@ -4,7 +4,7 @@ namespace Dapr.E2E.Test.Actors.Generators; public record RemoteState(string Value); -public interface IRemoteActor : IActor +public interface IRemoteActor : IRemotePingActor { Task GetState(); diff --git a/test/Dapr.E2E.Test.Actors.Generators/IRemotePingActor.cs b/test/Dapr.E2E.Test.Actors.Generators/IRemotePingActor.cs new file mode 100644 index 000000000..bd61b3d55 --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/IRemotePingActor.cs @@ -0,0 +1,8 @@ +using Dapr.Actors; + +namespace Dapr.E2E.Test.Actors.Generators; + +public interface IRemotePingActor : IActor +{ + Task Ping(); +} diff --git a/test/Dapr.E2E.Test.Actors.Generators/RemoteActor.cs b/test/Dapr.E2E.Test.Actors.Generators/RemoteActor.cs index 098de7374..74ba677ef 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/RemoteActor.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/RemoteActor.cs @@ -29,4 +29,9 @@ public Task SetState(RemoteState state) return Task.CompletedTask; } + + public Task Ping() + { + return Task.CompletedTask; + } } \ No newline at end of file From 3f850757630a58b990dedfdcfd7d87459c8038b3 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Wed, 11 Oct 2023 13:34:58 -0700 Subject: [PATCH 25/43] Cleanup test project. Signed-off-by: Phillip Hoff --- .../ActorState.cs | 27 +++++++ .../ActorWebApplicationFactory.cs | 15 +++- .../{ => Clients}/GeneratedClientTests.cs | 72 +++++++++---------- .../{ => Clients}/IClientActor.cs | 2 +- .../{ => Clients}/IRemoteActor.cs | 6 +- .../{ => Clients}/RemoteActor.cs | 2 +- .../{IRemotePingActor.cs => IPingActor.cs} | 2 +- .../PortManager.cs | 23 +++++- 8 files changed, 98 insertions(+), 51 deletions(-) create mode 100644 test/Dapr.E2E.Test.Actors.Generators/ActorState.cs rename test/Dapr.E2E.Test.Actors.Generators/{ => Clients}/GeneratedClientTests.cs (51%) rename test/Dapr.E2E.Test.Actors.Generators/{ => Clients}/IClientActor.cs (88%) rename test/Dapr.E2E.Test.Actors.Generators/{ => Clients}/IRemoteActor.cs (51%) rename test/Dapr.E2E.Test.Actors.Generators/{ => Clients}/RemoteActor.cs (93%) rename test/Dapr.E2E.Test.Actors.Generators/{IRemotePingActor.cs => IPingActor.cs} (66%) diff --git a/test/Dapr.E2E.Test.Actors.Generators/ActorState.cs b/test/Dapr.E2E.Test.Actors.Generators/ActorState.cs new file mode 100644 index 000000000..2053c78f1 --- /dev/null +++ b/test/Dapr.E2E.Test.Actors.Generators/ActorState.cs @@ -0,0 +1,27 @@ +using Dapr.Actors; +using Dapr.Actors.Client; + +namespace Dapr.E2E.Test.Actors.Generators; + +internal static class ActorState +{ + public static async Task EnsureReadyAsync(ActorId actorId, string actorType, ActorProxyOptions? options = null, CancellationToken cancellationToken = default) + where TActor : IPingActor + { + var pingProxy = ActorProxy.Create(actorId, actorType, options); + + while (true) + { + try + { + await pingProxy.Ping(); + + break; + } + catch (DaprApiException) + { + await Task.Delay(TimeSpan.FromMilliseconds(250), cancellationToken); + } + } + } +} diff --git a/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs b/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs index 3a1eed53d..fa95bb2da 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs @@ -4,7 +4,9 @@ namespace Dapr.E2E.Test.Actors.Generators; internal sealed record ActorWebApplicationOptions(Action ConfigureActors) { - public Action? ConfigureBuilder { get; init; } + public ILoggerProvider? LoggerProvider { get; init; } + + public string? Url { get; init; } } internal sealed class ActorWebApplicationFactory @@ -13,12 +15,21 @@ public static WebApplication Create(ActorWebApplicationOptions options) { var builder = WebApplication.CreateBuilder(); - options.ConfigureBuilder?.Invoke(builder); + if (options.LoggerProvider is not null) + { + builder.Logging.ClearProviders(); + builder.Logging.AddProvider(options.LoggerProvider); + } builder.Services.AddActors(options.ConfigureActors); var app = builder.Build(); + if (options.Url is not null) + { + app.Urls.Add(options.Url); + } + app.UseRouting(); #pragma warning disable ASP0014 diff --git a/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs b/test/Dapr.E2E.Test.Actors.Generators/Clients/GeneratedClientTests.cs similarity index 51% rename from test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs rename to test/Dapr.E2E.Test.Actors.Generators/Clients/GeneratedClientTests.cs index 95a2ef33c..e8d398b99 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/GeneratedClientTests.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/Clients/GeneratedClientTests.cs @@ -2,15 +2,15 @@ using Dapr.Actors.Client; using Xunit.Abstractions; -namespace Dapr.E2E.Test.Actors.Generators; +namespace Dapr.E2E.Test.Actors.Generators.Clients; public class GeneratedClientTests { - private readonly ITestOutputHelper testOutputHelper; + private readonly ILoggerProvider testLoggerProvider; public GeneratedClientTests(ITestOutputHelper testOutputHelper) { - this.testOutputHelper = testOutputHelper; + this.testLoggerProvider = new XUnitLoggingProvider(testOutputHelper); } [Fact] @@ -18,27 +18,24 @@ public async Task TestGeneratedClientAsync() { var portManager = new PortManager(); - var reservedPorts = portManager.ReservePorts(2).ToArray(); + (int appPort, int clientAppHttpPort) = portManager.ReservePorts(); - var appPort = reservedPorts[0]; - var clientAppHttpPort = reservedPorts[1]; - - var loggerProvider = new XUnitLoggingProvider(this.testOutputHelper); - var loggerFactory = new LoggerFactory(); - - loggerFactory.AddProvider(loggerProvider); - - var serviceAppSidecarOptions = new DaprSidecarOptions("service-app") + var templateSidecarOptions = new DaprSidecarOptions("template-app") { - AppPort = appPort, - LoggerFactory = loggerFactory, + LoggerFactory = new LoggerFactory(new[] { this.testLoggerProvider }), LogLevel = "debug" }; - var clientAppSidecarOptions = new DaprSidecarOptions("client-app") + var serviceAppSidecarOptions = templateSidecarOptions with + { + AppId = "service-app", + AppPort = appPort + }; + + var clientAppSidecarOptions = templateSidecarOptions with { - DaprHttpPort = clientAppHttpPort, - LoggerFactory = loggerFactory + AppId = "client-app", + DaprHttpPort = clientAppHttpPort }; await using var app = ActorWebApplicationFactory.Create( @@ -48,19 +45,22 @@ public async Task TestGeneratedClientAsync() options.Actors.RegisterActor(); }) { - ConfigureBuilder = builder => - { - builder.Logging.ClearProviders(); - builder.Logging.AddProvider(loggerProvider); - } + LoggerProvider = this.testLoggerProvider, + Url = $"http://localhost:{appPort}" }); - using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(1)); - app.Urls.Add($"http://localhost:{appPort}"); + // + // Start application... + // await app.StartAsync(cancellationTokenSource.Token); + // + // Start sidecars... + // + await using var serviceAppSidecar = DaprSidecarFactory.Create(serviceAppSidecarOptions); await serviceAppSidecar.StartAsync(cancellationTokenSource.Token); @@ -69,25 +69,19 @@ public async Task TestGeneratedClientAsync() await clientAppSidecar.StartAsync(cancellationTokenSource.Token); + // + // Ensure actor is ready... + // + var actorId = ActorId.CreateRandom(); var actorType = "RemoteActor"; var actorOptions = new ActorProxyOptions { HttpEndpoint = $"http://localhost:{clientAppHttpPort}" }; - var pingProxy = ActorProxy.Create(actorId, actorType, actorOptions); - - while (true) - { - try - { - await pingProxy.Ping(); + await ActorState.EnsureReadyAsync(actorId, actorType, actorOptions, cancellationTokenSource.Token); - break; - } - catch (DaprApiException) - { - await Task.Delay(TimeSpan.FromMilliseconds(250), cancellationTokenSource.Token); - } - } + // + // Start test... + // var actorProxy = ActorProxy.Create(actorId, actorType, actorOptions); diff --git a/test/Dapr.E2E.Test.Actors.Generators/IClientActor.cs b/test/Dapr.E2E.Test.Actors.Generators/Clients/IClientActor.cs similarity index 88% rename from test/Dapr.E2E.Test.Actors.Generators/IClientActor.cs rename to test/Dapr.E2E.Test.Actors.Generators/Clients/IClientActor.cs index fefe38f9e..9dc9ab20e 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/IClientActor.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/Clients/IClientActor.cs @@ -1,6 +1,6 @@ using Dapr.Actors.Generators; -namespace Dapr.E2E.Test.Actors.Generators; +namespace Dapr.E2E.Test.Actors.Generators.Clients; internal record ClientState(string Value); diff --git a/test/Dapr.E2E.Test.Actors.Generators/IRemoteActor.cs b/test/Dapr.E2E.Test.Actors.Generators/Clients/IRemoteActor.cs similarity index 51% rename from test/Dapr.E2E.Test.Actors.Generators/IRemoteActor.cs rename to test/Dapr.E2E.Test.Actors.Generators/Clients/IRemoteActor.cs index 44e31ba4e..7c3726b2f 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/IRemoteActor.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/Clients/IRemoteActor.cs @@ -1,10 +1,8 @@ -using Dapr.Actors; - -namespace Dapr.E2E.Test.Actors.Generators; +namespace Dapr.E2E.Test.Actors.Generators.Clients; public record RemoteState(string Value); -public interface IRemoteActor : IRemotePingActor +public interface IRemoteActor : IPingActor { Task GetState(); diff --git a/test/Dapr.E2E.Test.Actors.Generators/RemoteActor.cs b/test/Dapr.E2E.Test.Actors.Generators/Clients/RemoteActor.cs similarity index 93% rename from test/Dapr.E2E.Test.Actors.Generators/RemoteActor.cs rename to test/Dapr.E2E.Test.Actors.Generators/Clients/RemoteActor.cs index 74ba677ef..5db91d458 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/RemoteActor.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/Clients/RemoteActor.cs @@ -1,6 +1,6 @@ using Dapr.Actors.Runtime; -namespace Dapr.E2E.Test.Actors.Generators; +namespace Dapr.E2E.Test.Actors.Generators.Clients; internal sealed class RemoteActor : Actor, IRemoteActor { diff --git a/test/Dapr.E2E.Test.Actors.Generators/IRemotePingActor.cs b/test/Dapr.E2E.Test.Actors.Generators/IPingActor.cs similarity index 66% rename from test/Dapr.E2E.Test.Actors.Generators/IRemotePingActor.cs rename to test/Dapr.E2E.Test.Actors.Generators/IPingActor.cs index bd61b3d55..814ee770d 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/IRemotePingActor.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/IPingActor.cs @@ -2,7 +2,7 @@ namespace Dapr.E2E.Test.Actors.Generators; -public interface IRemotePingActor : IActor +public interface IPingActor : IActor { Task Ping(); } diff --git a/test/Dapr.E2E.Test.Actors.Generators/PortManager.cs b/test/Dapr.E2E.Test.Actors.Generators/PortManager.cs index 341e46210..23df8c27f 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/PortManager.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/PortManager.cs @@ -8,6 +8,20 @@ internal sealed class PortManager private readonly object reservationLock = new(); + public int ReservePort(int rangeStart = 55000) + { + var ports = this.ReservePorts(1, rangeStart); + + return ports.First(); + } + + public (int, int) ReservePorts(int rangeStart = 55000) + { + var ports = this.ReservePorts(2, rangeStart).ToArray(); + + return (ports[0], ports[1]); + } + public ISet ReservePorts(int count, int rangeStart = 55000) { lock (this.reservationLock) @@ -24,10 +38,13 @@ public ISet ReservePorts(int count, int rangeStart = 55000) Enumerable .Range(rangeStart, Int32.MaxValue - rangeStart + 1) .Where(port => !activePorts.Contains(port)) - .Where(port => !this.reservedPorts.Contains(port)) - .Take(count); + .Where(port => !this.reservedPorts.Contains(port)); + + var newReservedPorts = availablePorts.Take(count).ToHashSet(); + + this.reservedPorts.UnionWith(newReservedPorts); - return availablePorts.ToHashSet(); + return newReservedPorts; } } } \ No newline at end of file From 03435e83f2ebe61ec1d0a55e7cfcea9a51513e1c Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Wed, 11 Oct 2023 14:32:05 -0700 Subject: [PATCH 26/43] Add new E2E test project to workflow. Signed-off-by: Phillip Hoff --- .github/workflows/itests.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/itests.yml b/.github/workflows/itests.yml index 870264f40..6a7cfb1b9 100644 --- a/.github/workflows/itests.yml +++ b/.github/workflows/itests.yml @@ -124,8 +124,23 @@ jobs: /p:CollectCoverage=true \ /p:CoverletOutputFormat=opencover \ /p:GITHUB_ACTIONS=false + - name: Run Test + id: generator-tests + continue-on-error: true # proceed if tests fail, the report step will report the failure with more details. + run: | + dotnet test ${{ github.workspace }}/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj \ + --configuration Release \ + --framework ${{ matrix.framework }} \ + --no-build \ + --no-restore \ + --logger "trx;LogFilePrefix=${{ matrix.prefix }}" \ + --logger "GitHubActions;report-warnings=false" \ + --results-directory "${{ github.workspace }}/TestResults" \ + /p:CollectCoverage=true \ + /p:CoverletOutputFormat=opencover \ + /p:GITHUB_ACTIONS=false - name: Check test failure in PR - if: github.event_name == 'pull_request' && steps.tests.outcome != 'success' + if: github.event_name == 'pull_request' && (steps.tests.outcome != 'success' || steps.generator-tests.outcome != 'success') run: exit 1 - name: Upload test coverage uses: codecov/codecov-action@v1 From a98dc9a3f76e5fde46f2e05f366059c9804bcfca Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Wed, 11 Oct 2023 14:47:39 -0700 Subject: [PATCH 27/43] Attempt to log generator test output. Signed-off-by: Phillip Hoff --- .github/workflows/itests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/itests.yml b/.github/workflows/itests.yml index 6a7cfb1b9..1c3498f9c 100644 --- a/.github/workflows/itests.yml +++ b/.github/workflows/itests.yml @@ -109,7 +109,7 @@ jobs: - name: Build # disable deterministic builds, just for test run. Deterministic builds break coverage for some reason run: dotnet build --configuration release /p:GITHUB_ACTIONS=false - - name: Run Test + - name: Run General Test id: tests continue-on-error: true # proceed if tests fail, the report step will report the failure with more details. run: | @@ -124,7 +124,7 @@ jobs: /p:CollectCoverage=true \ /p:CoverletOutputFormat=opencover \ /p:GITHUB_ACTIONS=false - - name: Run Test + - name: Run Generator Test id: generator-tests continue-on-error: true # proceed if tests fail, the report step will report the failure with more details. run: | @@ -134,7 +134,7 @@ jobs: --no-build \ --no-restore \ --logger "trx;LogFilePrefix=${{ matrix.prefix }}" \ - --logger "GitHubActions;report-warnings=false" \ + --logger "GitHubActions;report-warnings=false;verbosity=detailed" \ --results-directory "${{ github.workspace }}/TestResults" \ /p:CollectCoverage=true \ /p:CoverletOutputFormat=opencover \ From 44a5d1b56c0c7f856695631e5587bc941c18fbc0 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Wed, 11 Oct 2023 15:03:45 -0700 Subject: [PATCH 28/43] Try logging tweaks. Signed-off-by: Phillip Hoff --- .github/workflows/itests.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/itests.yml b/.github/workflows/itests.yml index 1c3498f9c..cf5edf65f 100644 --- a/.github/workflows/itests.yml +++ b/.github/workflows/itests.yml @@ -109,7 +109,7 @@ jobs: - name: Build # disable deterministic builds, just for test run. Deterministic builds break coverage for some reason run: dotnet build --configuration release /p:GITHUB_ACTIONS=false - - name: Run General Test + - name: Run General Tests id: tests continue-on-error: true # proceed if tests fail, the report step will report the failure with more details. run: | @@ -124,7 +124,7 @@ jobs: /p:CollectCoverage=true \ /p:CoverletOutputFormat=opencover \ /p:GITHUB_ACTIONS=false - - name: Run Generator Test + - name: Run Generators Tests id: generator-tests continue-on-error: true # proceed if tests fail, the report step will report the failure with more details. run: | @@ -134,7 +134,8 @@ jobs: --no-build \ --no-restore \ --logger "trx;LogFilePrefix=${{ matrix.prefix }}" \ - --logger "GitHubActions;report-warnings=false;verbosity=detailed" \ + --logger "GitHubActions;report-warnings=false" \ + --logger "console;verbosity=detailed" \ --results-directory "${{ github.workspace }}/TestResults" \ /p:CollectCoverage=true \ /p:CoverletOutputFormat=opencover \ From 770c1bf1f0ffa1e6d4a15378cc7205c3481e5a92 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Wed, 11 Oct 2023 16:23:29 -0700 Subject: [PATCH 29/43] Start cleanup of sample project. Signed-off-by: Phillip Hoff --- .../ActorClient/IClientActor.cs | 15 +++ .../ActorClient/IMyPrivateActor.cs | 17 ---- .../GeneratedActor/ActorClient/Program.cs | 91 ++----------------- .../ActorCommon/ActorCommon.csproj | 6 -- .../ActorCommon/IMyPublicActor.cs | 15 --- .../ActorCommon/IRemoteActor.cs | 12 +++ .../ActorCommon/MyPublicActorManualProxy.cs | 24 ----- .../ActorService/ActorService.csproj | 5 - .../GeneratedActor/ActorService/Program.cs | 39 +------- .../{MyPublicActor.cs => RemoteActor.cs} | 16 ++-- examples/GeneratedActor/README.md | 58 ++++++++++++ 11 files changed, 99 insertions(+), 199 deletions(-) create mode 100644 examples/GeneratedActor/ActorClient/IClientActor.cs delete mode 100644 examples/GeneratedActor/ActorClient/IMyPrivateActor.cs delete mode 100644 examples/GeneratedActor/ActorCommon/IMyPublicActor.cs create mode 100644 examples/GeneratedActor/ActorCommon/IRemoteActor.cs delete mode 100644 examples/GeneratedActor/ActorCommon/MyPublicActorManualProxy.cs rename examples/GeneratedActor/ActorService/{MyPublicActor.cs => RemoteActor.cs} (51%) create mode 100644 examples/GeneratedActor/README.md diff --git a/examples/GeneratedActor/ActorClient/IClientActor.cs b/examples/GeneratedActor/ActorClient/IClientActor.cs new file mode 100644 index 000000000..eaf32c319 --- /dev/null +++ b/examples/GeneratedActor/ActorClient/IClientActor.cs @@ -0,0 +1,15 @@ +using Dapr.Actors.Generators; + +namespace GeneratedActor; + +internal sealed record ClientState(string Value); + +[GenerateActorClient] +internal interface IClientActor +{ + [ActorMethod(Name = "GetState")] + Task GetStateAsync(CancellationToken cancellationToken = default); + + [ActorMethod(Name = "SetState")] + Task SetStateAsync(ClientState state, CancellationToken cancellationToken = default); +} diff --git a/examples/GeneratedActor/ActorClient/IMyPrivateActor.cs b/examples/GeneratedActor/ActorClient/IMyPrivateActor.cs deleted file mode 100644 index e4af061d4..000000000 --- a/examples/GeneratedActor/ActorClient/IMyPrivateActor.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Dapr.Actors.Generators; - -namespace GeneratedActor; - -internal sealed record MyPrivateState(string Name); - -/// -/// Non-remoted invocations have a strict limit on a single argument; CancellationToken is not supported. -/// -[GenerateActorClient] -internal interface IMyPrivateActor -{ - [ActorMethod(Name = "GetStateAsync")] - Task GetPrivateStateAsync(); - - Task SetStateAsync(MyPrivateState state); -} diff --git a/examples/GeneratedActor/ActorClient/Program.cs b/examples/GeneratedActor/ActorClient/Program.cs index 37f8fe253..895ceb9f4 100644 --- a/examples/GeneratedActor/ActorClient/Program.cs +++ b/examples/GeneratedActor/ActorClient/Program.cs @@ -2,93 +2,16 @@ using Dapr.Actors.Client; using GeneratedActor; -var actorId = new ActorId("1"); -var actorType = "MyPublicActor"; +Console.WriteLine("Testing generated client..."); -try -{ - //await TestRemotedActorAsync(); -} -catch (Exception ex) -{ - Console.WriteLine(ex); -} +var proxy = ActorProxy.Create(ActorId.CreateRandom(), "RemoteActor"); -try -{ - await TestNonRemotedActorAsync(); -} -catch (Exception ex) -{ - Console.WriteLine(ex); -} +using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); -try -{ - await TestNonRemotedManualProxyAsync(); -} -catch (Exception ex) -{ - Console.WriteLine(ex); -} +var client = new ClientActorClient(proxy); -try -{ - await TestGeneratedProxyAsync(); -} -catch (Exception ex) -{ - Console.WriteLine(ex); -} +var state = await client.GetStateAsync(cancellationTokenSource.Token); -Console.WriteLine("Hello, World!"); +await client.SetStateAsync(new ClientState("Hello, World!"), cancellationTokenSource.Token); -/* -async Task TestRemotedActorAsync() -{ - Console.WriteLine("Testing remoted actor client..."); - - var client = ActorProxy.Create(actorId, actorType); - - var state = await client.GetStateAsync(); - - await client.SetStateAsync(new MyState("Hello, World!")); -} -*/ - -async Task TestNonRemotedActorAsync() -{ - Console.WriteLine("Testing non-remoted actor client..."); - - var client = ActorProxy.Create(actorId, actorType /*, new ActorProxyOptions { UseJsonSerialization = true } */); - - var state = await client.InvokeMethodAsync("GetStateAsync"); - - await client.InvokeMethodAsync("SetStateAsync", new MyState("Hello, World!")); -} - -async Task TestNonRemotedManualProxyAsync() -{ - Console.WriteLine("Testing non-remoted manual proxy..."); - - var proxy = ActorProxy.Create(actorId, actorType /*, new ActorProxyOptions { UseJsonSerialization = true } */); - - var client = new MyPublicActorManualProxy(proxy); - - var state = await client.GetStateAsync(); - - await client.SetStateAsync(new MyState("Hello, World!")); -} - -async Task TestGeneratedProxyAsync() -{ - Console.WriteLine("Testing generated proxy..."); - - var proxy = ActorProxy.Create(actorId, actorType /*, new ActorProxyOptions { UseJsonSerialization = true } */); - - var client = new MyPrivateActorClient(proxy); - - var state = await client.GetPrivateStateAsync(); - - await client.SetStateAsync(new MyPrivateState("Hello, World!")); -} +Console.WriteLine("Done!"); diff --git a/examples/GeneratedActor/ActorCommon/ActorCommon.csproj b/examples/GeneratedActor/ActorCommon/ActorCommon.csproj index 69333942f..2ccc1581b 100644 --- a/examples/GeneratedActor/ActorCommon/ActorCommon.csproj +++ b/examples/GeneratedActor/ActorCommon/ActorCommon.csproj @@ -11,10 +11,4 @@ - - - - diff --git a/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs b/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs deleted file mode 100644 index b96a239c2..000000000 --- a/examples/GeneratedActor/ActorCommon/IMyPublicActor.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Dapr.Actors; - -namespace GeneratedActor; - -public sealed record MyState(string Name); - -/// -/// Non-remoted invocations have a strict limit on a single argument; CancellationToken is not supported. -/// -public interface IMyPublicActor : IActor -{ - Task GetStateAsync(); - - Task SetStateAsync(MyState state); -} diff --git a/examples/GeneratedActor/ActorCommon/IRemoteActor.cs b/examples/GeneratedActor/ActorCommon/IRemoteActor.cs new file mode 100644 index 000000000..a4e2a60e4 --- /dev/null +++ b/examples/GeneratedActor/ActorCommon/IRemoteActor.cs @@ -0,0 +1,12 @@ +using Dapr.Actors; + +namespace GeneratedActor; + +public sealed record RemoteState(string Value); + +public interface IRemoteActor : IActor +{ + Task GetState(); + + Task SetState(RemoteState state); +} diff --git a/examples/GeneratedActor/ActorCommon/MyPublicActorManualProxy.cs b/examples/GeneratedActor/ActorCommon/MyPublicActorManualProxy.cs deleted file mode 100644 index 711b6f653..000000000 --- a/examples/GeneratedActor/ActorCommon/MyPublicActorManualProxy.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Dapr.Actors; -using Dapr.Actors.Client; - -namespace GeneratedActor; - -public sealed class MyPublicActorManualProxy : IMyPublicActor -{ - private readonly ActorProxy actorProxy; - - public MyPublicActorManualProxy(ActorProxy actorProxy) - { - this.actorProxy = actorProxy; - } - - public Task GetStateAsync() - { - return this.actorProxy.InvokeMethodAsync("GetStateAsync"); - } - - public Task SetStateAsync(MyState state) - { - return this.actorProxy.InvokeMethodAsync("SetStateAsync", state); - } -} diff --git a/examples/GeneratedActor/ActorService/ActorService.csproj b/examples/GeneratedActor/ActorService/ActorService.csproj index 60b541436..ac540884d 100644 --- a/examples/GeneratedActor/ActorService/ActorService.csproj +++ b/examples/GeneratedActor/ActorService/ActorService.csproj @@ -12,9 +12,4 @@ - - - - - diff --git a/examples/GeneratedActor/ActorService/Program.cs b/examples/GeneratedActor/ActorService/Program.cs index 98cc20c10..61e3b0be7 100644 --- a/examples/GeneratedActor/ActorService/Program.cs +++ b/examples/GeneratedActor/ActorService/Program.cs @@ -2,16 +2,11 @@ var builder = WebApplication.CreateBuilder(args); -// Add services to the container. -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); - builder.Services.AddActors( options => { options.UseJsonSerialization = true; - options.Actors.RegisterActor(); + options.Actors.RegisterActor(); }); var app = builder.Build(); @@ -25,36 +20,4 @@ endpoints.MapActorsHandlers(); }); -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} - -var summaries = new[] -{ - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" -}; - -app.MapGet("/weatherforecast", () => -{ - var forecast = Enumerable.Range(1, 5).Select(index => - new WeatherForecast - ( - DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - Random.Shared.Next(-20, 55), - summaries[Random.Shared.Next(summaries.Length)] - )) - .ToArray(); - return forecast; -}) -.WithName("GetWeatherForecast") -.WithOpenApi(); - app.Run(); - -record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) -{ - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); -} diff --git a/examples/GeneratedActor/ActorService/MyPublicActor.cs b/examples/GeneratedActor/ActorService/RemoteActor.cs similarity index 51% rename from examples/GeneratedActor/ActorService/MyPublicActor.cs rename to examples/GeneratedActor/ActorService/RemoteActor.cs index a5777e25e..354af8e51 100644 --- a/examples/GeneratedActor/ActorService/MyPublicActor.cs +++ b/examples/GeneratedActor/ActorService/RemoteActor.cs @@ -2,28 +2,26 @@ namespace GeneratedActor; -internal sealed class MyPublicActor : Actor, IMyPublicActor +internal sealed class RemoteActor : Actor, IRemoteActor { - private readonly ILogger logger; + private readonly ILogger logger; - private MyState currentState = new("default"); + private RemoteState currentState = new("default"); - public MyPublicActor(ActorHost host, ILogger logger) + public RemoteActor(ActorHost host, ILogger logger) : base(host) { this.logger = logger; } - #region IMyPublicActor Members - - public Task GetStateAsync() + public Task GetState() { this.logger.LogInformation("GetStateAsync called."); return Task.FromResult(this.currentState); } - public Task SetStateAsync(MyState state) + public Task SetState(RemoteState state) { this.logger.LogInformation("SetStateAsync called."); @@ -31,6 +29,4 @@ public Task SetStateAsync(MyState state) return Task.CompletedTask; } - - #endregion } \ No newline at end of file diff --git a/examples/GeneratedActor/README.md b/examples/GeneratedActor/README.md new file mode 100644 index 000000000..a89466e6b --- /dev/null +++ b/examples/GeneratedActor/README.md @@ -0,0 +1,58 @@ +# Generated Actor Client Example + +An example of generating a strongly-typed actor client. + +## Prerequisites + +- [.NET 6+](https://dotnet.microsoft.com/download) installed +- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) +- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) +- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) + +## Run the example + +### Start the ActorService + +Change directory to the `ActorService` folder: + +```bash +cd examples/GeneratedActor/ActorService +``` + +To start the `ActorService`, execute the following command: + +```bash +dapr run --app-id generated-service --app-port 5226 -- dotnet run +``` + +### Run the ActorClient + +Change directory to the `ActorClient` folder: + +```bash +cd examples/GeneratedActor/ActorClient +``` + +To run the `ActorClient`, execute the following command: + +```bash +dapr run --app-id generated-client -- dotnet run +``` + +### Expected output + +You should see the following output from the `ActorClient`: + +``` +== APP == Testing generated client... +== APP == Done! +``` + +You should see also see the following output from the `ActorService`: + +``` +== APP == info: GeneratedActor.RemoteActor[0] +== APP == GetStateAsync called. +== APP == info: GeneratedActor.RemoteActor[0] +== APP == SetStateAsync called. +``` \ No newline at end of file From 50ab98fc3f74ba70066fdbeb415af11f7c1c97b1 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 12 Oct 2023 09:23:32 -0700 Subject: [PATCH 30/43] Update sample README. Signed-off-by: Phillip Hoff --- examples/GeneratedActor/README.md | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/examples/GeneratedActor/README.md b/examples/GeneratedActor/README.md index a89466e6b..2a0a60f81 100644 --- a/examples/GeneratedActor/README.md +++ b/examples/GeneratedActor/README.md @@ -9,6 +9,65 @@ An example of generating a strongly-typed actor client. - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) - [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) +## Overview + +Two options for invoking actor methods exist in the Dapr .NET SDK, a strongly-type (remoting) option and a loosely-typed (non-remoting) option. Each has its own advantages and disadvantages. A "middle" option also exists that combines the two and gains benefits of both without some of the disadvantages of either. Using .NET Source Generators, the Dapr .NET SDK can generate a strongly-typed client implementation that uses loosely-typed method invocation under the covers. + +Strongly-typed clients are generated by: + +1. Referencing the `Dapr.Actors.Generators` NuGet package with `OutputItemType` set to `Analyzer` and `ReferenceOutputAssembly` set to `false`. + + ```xml + + + + + + ``` + +1. Add the `Dapr.Actors.Generators.GenerateActorClientAttribute` to the actor interface. + + ```csharp + using Dapr.Actors.Generators; + + namespace Sample; + + internal sealed record SampleState(string Value); + + [GenerateActorClient] + internal interface ISampleActor + { + [ActorMethod(Name = "GetState")] + Task GetStateAsync(CancellationToken cancellationToken = default); + + [ActorMethod(Name = "SetState")] + Task SetStateAsync(SampleState state, CancellationToken cancellationToken = default); + } + ``` + + > The `Dapr.Actors.Generators.ActorMethodAttribute` can be used to map interface methods definitions to specific actor methods should the names differ (e.g. the interface uses "Async" suffix common in .NET but the actor methods do not). + +1. A strongly-typed client will be generated that can be used to invoke actor methods. + + ```csharp + using Dapr.Actors; + using Dapr.Actors.Client; + using Sample; + + var proxy = ActorProxy.Create(ActorId.CreateRandom(), "SampleActor"); + + using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + + var client = new SampleActorClient(proxy); + + var state = await client.GetStateAsync(cancellationTokenSource.Token); + + await client.SetStateAsync(new SampleState("Hello, World!"), cancellationTokenSource.Token); + + ``` + ## Run the example ### Start the ActorService From 83f0ce57c9e5c7293c559748d1718c42d238f59e Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 12 Oct 2023 09:48:29 -0700 Subject: [PATCH 31/43] Update generators project. Signed-off-by: Phillip Hoff --- .../Dapr.Actors.Generators.csproj | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj index 3e888330b..3ce4c8526 100644 --- a/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj +++ b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj @@ -1,7 +1,6 @@ - netstandard2.0 10.0 enable enable @@ -16,4 +15,21 @@ + + + + + netstandard2.0 + + + false + + + + + + + From dca76b91ac5b5b4cb72daa86e67268d5905ef179 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 12 Oct 2023 12:51:37 -0700 Subject: [PATCH 32/43] Tweak NuGet package creation. Signed-off-by: Phillip Hoff --- .../Dapr.Actors.Generators.csproj | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj index 3ce4c8526..9fcc58893 100644 --- a/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj +++ b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj @@ -25,6 +25,16 @@ false + + + true + + + false + + + This package contains source generators for interacting with Actor services using Dapr. + $(PackageTags);Actors From 8af14b910df26991f88b75c2dc30e30285f369e7 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 12 Oct 2023 13:18:16 -0700 Subject: [PATCH 33/43] Add copyright headers. Signed-off-by: Phillip Hoff --- .../GeneratedActor/ActorClient/IClientActor.cs | 13 +++++++++++++ examples/GeneratedActor/ActorClient/Program.cs | 15 ++++++++++++++- .../GeneratedActor/ActorCommon/IRemoteActor.cs | 13 +++++++++++++ examples/GeneratedActor/ActorService/Program.cs | 13 +++++++++++++ .../GeneratedActor/ActorService/RemoteActor.cs | 13 +++++++++++++ .../ActorClientGenerator.cs | 14 +++++++++++++- .../ActorClientGeneratorTests.cs | 13 +++++++++++++ .../AdditionalMetadataReferences.cs | 13 +++++++++++++ .../CSharpSourceGeneratorVerifier.cs | 13 +++++++++++++ test/Dapr.Actors.Generators.Test/GlobalUsings.cs | 13 +++++++++++++ .../Dapr.E2E.Test.Actors.Generators/ActorState.cs | 13 +++++++++++++ .../ActorWebApplicationFactory.cs | 13 +++++++++++++ .../Clients/GeneratedClientTests.cs | 13 +++++++++++++ .../Clients/IClientActor.cs | 13 +++++++++++++ .../Clients/IRemoteActor.cs | 13 +++++++++++++ .../Clients/RemoteActor.cs | 13 +++++++++++++ .../DaprSidecarFactory.cs | 13 +++++++++++++ .../GlobalUsings.cs | 13 +++++++++++++ .../Dapr.E2E.Test.Actors.Generators/IPingActor.cs | 13 +++++++++++++ .../PortManager.cs | 13 +++++++++++++ .../XUnitLoggingProvider.cs | 13 +++++++++++++ 21 files changed, 274 insertions(+), 2 deletions(-) diff --git a/examples/GeneratedActor/ActorClient/IClientActor.cs b/examples/GeneratedActor/ActorClient/IClientActor.cs index eaf32c319..c5c732cb9 100644 --- a/examples/GeneratedActor/ActorClient/IClientActor.cs +++ b/examples/GeneratedActor/ActorClient/IClientActor.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using Dapr.Actors.Generators; namespace GeneratedActor; diff --git a/examples/GeneratedActor/ActorClient/Program.cs b/examples/GeneratedActor/ActorClient/Program.cs index 895ceb9f4..87f714907 100644 --- a/examples/GeneratedActor/ActorClient/Program.cs +++ b/examples/GeneratedActor/ActorClient/Program.cs @@ -1,4 +1,17 @@ -using Dapr.Actors; +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Actors; using Dapr.Actors.Client; using GeneratedActor; diff --git a/examples/GeneratedActor/ActorCommon/IRemoteActor.cs b/examples/GeneratedActor/ActorCommon/IRemoteActor.cs index a4e2a60e4..6d136a704 100644 --- a/examples/GeneratedActor/ActorCommon/IRemoteActor.cs +++ b/examples/GeneratedActor/ActorCommon/IRemoteActor.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using Dapr.Actors; namespace GeneratedActor; diff --git a/examples/GeneratedActor/ActorService/Program.cs b/examples/GeneratedActor/ActorService/Program.cs index 61e3b0be7..f6e62f720 100644 --- a/examples/GeneratedActor/ActorService/Program.cs +++ b/examples/GeneratedActor/ActorService/Program.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using GeneratedActor; var builder = WebApplication.CreateBuilder(args); diff --git a/examples/GeneratedActor/ActorService/RemoteActor.cs b/examples/GeneratedActor/ActorService/RemoteActor.cs index 354af8e51..f04921f69 100644 --- a/examples/GeneratedActor/ActorService/RemoteActor.cs +++ b/examples/GeneratedActor/ActorService/RemoteActor.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using Dapr.Actors.Runtime; namespace GeneratedActor; diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs index 1a2ee9582..349d80188 100644 --- a/src/Dapr.Actors.Generators/ActorClientGenerator.cs +++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs @@ -1,4 +1,16 @@ -using System.Linq; +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs index 4a7f7d4b9..ce4c0accd 100644 --- a/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs +++ b/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + namespace Dapr.Actors.Generators; using System.Text; diff --git a/test/Dapr.Actors.Generators.Test/AdditionalMetadataReferences.cs b/test/Dapr.Actors.Generators.Test/AdditionalMetadataReferences.cs index 9c2de7404..afa557026 100644 --- a/test/Dapr.Actors.Generators.Test/AdditionalMetadataReferences.cs +++ b/test/Dapr.Actors.Generators.Test/AdditionalMetadataReferences.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using Microsoft.CodeAnalysis; namespace Dapr.Actors.Generators; diff --git a/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs b/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs index a7d8c267d..de334b4ef 100644 --- a/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs +++ b/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/test/Dapr.Actors.Generators.Test/GlobalUsings.cs b/test/Dapr.Actors.Generators.Test/GlobalUsings.cs index 8c927eb74..48f0c59b2 100644 --- a/test/Dapr.Actors.Generators.Test/GlobalUsings.cs +++ b/test/Dapr.Actors.Generators.Test/GlobalUsings.cs @@ -1 +1,14 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + global using Xunit; \ No newline at end of file diff --git a/test/Dapr.E2E.Test.Actors.Generators/ActorState.cs b/test/Dapr.E2E.Test.Actors.Generators/ActorState.cs index 2053c78f1..6965c751c 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/ActorState.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/ActorState.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using Dapr.Actors; using Dapr.Actors.Client; diff --git a/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs b/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs index fa95bb2da..b5e81b8aa 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/ActorWebApplicationFactory.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using Dapr.Actors.Runtime; namespace Dapr.E2E.Test.Actors.Generators; diff --git a/test/Dapr.E2E.Test.Actors.Generators/Clients/GeneratedClientTests.cs b/test/Dapr.E2E.Test.Actors.Generators/Clients/GeneratedClientTests.cs index e8d398b99..6079b5df7 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/Clients/GeneratedClientTests.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/Clients/GeneratedClientTests.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using Dapr.Actors; using Dapr.Actors.Client; using Xunit.Abstractions; diff --git a/test/Dapr.E2E.Test.Actors.Generators/Clients/IClientActor.cs b/test/Dapr.E2E.Test.Actors.Generators/Clients/IClientActor.cs index 9dc9ab20e..a6cf30a76 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/Clients/IClientActor.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/Clients/IClientActor.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using Dapr.Actors.Generators; namespace Dapr.E2E.Test.Actors.Generators.Clients; diff --git a/test/Dapr.E2E.Test.Actors.Generators/Clients/IRemoteActor.cs b/test/Dapr.E2E.Test.Actors.Generators/Clients/IRemoteActor.cs index 7c3726b2f..77ad6e75b 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/Clients/IRemoteActor.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/Clients/IRemoteActor.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + namespace Dapr.E2E.Test.Actors.Generators.Clients; public record RemoteState(string Value); diff --git a/test/Dapr.E2E.Test.Actors.Generators/Clients/RemoteActor.cs b/test/Dapr.E2E.Test.Actors.Generators/Clients/RemoteActor.cs index 5db91d458..9c049019d 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/Clients/RemoteActor.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/Clients/RemoteActor.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using Dapr.Actors.Runtime; namespace Dapr.E2E.Test.Actors.Generators.Clients; diff --git a/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs b/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs index d759799aa..56d1954d4 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/DaprSidecarFactory.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using System.Diagnostics; namespace Dapr.E2E.Test.Actors.Generators; diff --git a/test/Dapr.E2E.Test.Actors.Generators/GlobalUsings.cs b/test/Dapr.E2E.Test.Actors.Generators/GlobalUsings.cs index 8c927eb74..48f0c59b2 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/GlobalUsings.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/GlobalUsings.cs @@ -1 +1,14 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + global using Xunit; \ No newline at end of file diff --git a/test/Dapr.E2E.Test.Actors.Generators/IPingActor.cs b/test/Dapr.E2E.Test.Actors.Generators/IPingActor.cs index 814ee770d..484c4d150 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/IPingActor.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/IPingActor.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using Dapr.Actors; namespace Dapr.E2E.Test.Actors.Generators; diff --git a/test/Dapr.E2E.Test.Actors.Generators/PortManager.cs b/test/Dapr.E2E.Test.Actors.Generators/PortManager.cs index 23df8c27f..fcd296977 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/PortManager.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/PortManager.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using System.Net.NetworkInformation; namespace Dapr.E2E.Test.Actors.Generators; diff --git a/test/Dapr.E2E.Test.Actors.Generators/XUnitLoggingProvider.cs b/test/Dapr.E2E.Test.Actors.Generators/XUnitLoggingProvider.cs index 0625d1b14..641d66d80 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/XUnitLoggingProvider.cs +++ b/test/Dapr.E2E.Test.Actors.Generators/XUnitLoggingProvider.cs @@ -1,3 +1,16 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + using Xunit.Abstractions; namespace Dapr.E2E.Test.Actors.Generators; From 7ef13eab8c4ab52d4fd03413eb02409876354c2e Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 12 Oct 2023 13:45:25 -0700 Subject: [PATCH 34/43] Update package reference instructions. Signed-off-by: Phillip Hoff --- examples/GeneratedActor/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/GeneratedActor/README.md b/examples/GeneratedActor/README.md index 2a0a60f81..cd595b30e 100644 --- a/examples/GeneratedActor/README.md +++ b/examples/GeneratedActor/README.md @@ -15,14 +15,12 @@ Two options for invoking actor methods exist in the Dapr .NET SDK, a strongly-ty Strongly-typed clients are generated by: -1. Referencing the `Dapr.Actors.Generators` NuGet package with `OutputItemType` set to `Analyzer` and `ReferenceOutputAssembly` set to `false`. +1. Referencing the `Dapr.Actors.Generators` NuGet package. ```xml - + ``` From 5e1cb7d260fa26746fabc022c38fd5318d567ff1 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 30 Nov 2023 16:03:52 -0800 Subject: [PATCH 35/43] Resolve build issue after merge. Signed-off-by: Phillip Hoff --- src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj index 9fcc58893..f62861735 100644 --- a/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj +++ b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj @@ -21,7 +21,7 @@ - netstandard2.0 + netstandard2.0 false From fb7a18ed238f59705b45fdd893b9d0a6775a33c8 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Thu, 30 Nov 2023 16:25:21 -0800 Subject: [PATCH 36/43] Reset target frameworks. Signed-off-by: Phillip Hoff --- src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj index f62861735..0ec716968 100644 --- a/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj +++ b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj @@ -21,7 +21,8 @@ - netstandard2.0 + netstandard2.0 + false From ae247255817b1a11d5d66669f8b47a0ab7d4b851 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Fri, 1 Dec 2023 15:58:27 -0800 Subject: [PATCH 37/43] Strip common settings out of project files. Signed-off-by: Phillip Hoff --- src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj | 1 - .../Dapr.Actors.Generators.Test.csproj | 1 - .../Dapr.E2E.Test.Actors.Generators.csproj | 2 -- 3 files changed, 4 deletions(-) diff --git a/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj index 0ec716968..a69f2d1a0 100644 --- a/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj +++ b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj @@ -1,7 +1,6 @@ - 10.0 enable enable diff --git a/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj b/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj index e4be63f57..851e0f0e7 100644 --- a/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj +++ b/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj @@ -2,7 +2,6 @@ net6;net7 - 10.0 enable enable diff --git a/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj b/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj index 8c44ec87b..8618647cb 100644 --- a/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj +++ b/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj @@ -1,8 +1,6 @@ - net6;net7 - 10.0 enable enable From 8333c560f2fe9afbc52577a88557a7362a8e4c02 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Mon, 4 Dec 2023 11:30:06 -0800 Subject: [PATCH 38/43] Retry build. Signed-off-by: Phillip Hoff From fab8e9d9b03ac0235f9494253e65c18e8175f5aa Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Mon, 4 Dec 2023 11:35:52 -0800 Subject: [PATCH 39/43] Retry build (again). Signed-off-by: Phillip Hoff From 8d9e00a2b884145d95093899e0e9bcd35434965b Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Mon, 4 Dec 2023 13:16:34 -0800 Subject: [PATCH 40/43] Update reference assemblies for .NET 8. Signed-off-by: Phillip Hoff --- .../CSharpSourceGeneratorVerifier.cs | 27 +++++++++++++++++-- .../Dapr.Actors.Generators.Test.csproj | 8 +++--- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs b/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs index de334b4ef..435488c2c 100644 --- a/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs +++ b/test/Dapr.Actors.Generators.Test/CSharpSourceGeneratorVerifier.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Testing.Verifiers; /// @@ -27,8 +28,30 @@ public class Test : CSharpSourceGeneratorTest { public Test() { - // NOTE: There's no Net70 yet, so we're using Net60 for now. Hopefully it "just works". - this.ReferenceAssemblies = Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net.Net60; + int frameworkVersion = + #if NET6_0 + 6; + #elif NET7_0 + 7; + #elif NET8_0 + 8; + #endif + + // + // NOTE: Ordinarily we'd use the following: + // + // this.ReferenceAssemblies = Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net.Net60; + // + // However, Net70 and Net80 are not yet available in the current version of the Roslyn SDK. + // + + this.ReferenceAssemblies = + new ReferenceAssemblies( + $"net{frameworkVersion}.0", + new PackageIdentity( + "Microsoft.NETCore.App.Ref", + $"{frameworkVersion}.0.0"), + Path.Combine("ref", $"net{frameworkVersion}.0")); } protected override CompilationOptions CreateCompilationOptions() diff --git a/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj b/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj index 851e0f0e7..212faed2d 100644 --- a/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj +++ b/test/Dapr.Actors.Generators.Test/Dapr.Actors.Generators.Test.csproj @@ -1,11 +1,9 @@ - net6;net7 enable enable - false true @@ -15,10 +13,10 @@ - - + + - + From 96fe90ca652ce79a2ea38c708cc741373b7fca09 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Mon, 4 Dec 2023 13:57:30 -0800 Subject: [PATCH 41/43] Add breathing room to timer test. Signed-off-by: Phillip Hoff --- test/Dapr.E2E.Test.App/Actors/TimerActor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Dapr.E2E.Test.App/Actors/TimerActor.cs b/test/Dapr.E2E.Test.App/Actors/TimerActor.cs index bbe6cf7ae..ddd6397f7 100644 --- a/test/Dapr.E2E.Test.App/Actors/TimerActor.cs +++ b/test/Dapr.E2E.Test.App/Actors/TimerActor.cs @@ -39,7 +39,7 @@ public Task GetState() public async Task StartTimer(StartTimerOptions options) { var bytes = JsonSerializer.SerializeToUtf8Bytes(options, this.Host.JsonSerializerOptions); - await this.RegisterTimerAsync("test-timer", nameof(Tick), bytes, dueTime: TimeSpan.Zero, period: TimeSpan.FromMilliseconds(50)); + await this.RegisterTimerAsync("test-timer", nameof(Tick), bytes, dueTime: TimeSpan.Zero, period: TimeSpan.FromMilliseconds(100)); await this.StateManager.SetStateAsync("timer-state", new State(){ IsTimerRunning = true, }); } From ea54e96c991b72b23cc28008811e276c9dd6d839 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Mon, 4 Dec 2023 14:05:11 -0800 Subject: [PATCH 42/43] Bump tests. Signed-off-by: Phillip Hoff From 15ca8c771d41d24bc8851d595866c2bb2d18e838 Mon Sep 17 00:00:00 2001 From: Phillip Hoff Date: Wed, 14 Feb 2024 12:27:21 -0800 Subject: [PATCH 43/43] Use .NET 6 for examples. Signed-off-by: Phillip Hoff --- examples/GeneratedActor/ActorClient/ActorClient.csproj | 2 +- examples/GeneratedActor/ActorCommon/ActorCommon.csproj | 2 +- examples/GeneratedActor/ActorService/ActorService.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/GeneratedActor/ActorClient/ActorClient.csproj b/examples/GeneratedActor/ActorClient/ActorClient.csproj index 4a11a7cf9..73b5c2027 100644 --- a/examples/GeneratedActor/ActorClient/ActorClient.csproj +++ b/examples/GeneratedActor/ActorClient/ActorClient.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net6 10.0 enable enable diff --git a/examples/GeneratedActor/ActorCommon/ActorCommon.csproj b/examples/GeneratedActor/ActorCommon/ActorCommon.csproj index 2ccc1581b..2cbc61e2c 100644 --- a/examples/GeneratedActor/ActorCommon/ActorCommon.csproj +++ b/examples/GeneratedActor/ActorCommon/ActorCommon.csproj @@ -1,7 +1,7 @@ - net7.0 + net6 10.0 enable enable diff --git a/examples/GeneratedActor/ActorService/ActorService.csproj b/examples/GeneratedActor/ActorService/ActorService.csproj index ac540884d..a74104363 100644 --- a/examples/GeneratedActor/ActorService/ActorService.csproj +++ b/examples/GeneratedActor/ActorService/ActorService.csproj @@ -1,7 +1,7 @@ - net7.0 + net6 10.0 enable enable