dotnet-sdk/test/Dapr.Actors.Generators.Test/ActorClientGeneratorTests.cs

881 lines
24 KiB
C#

// ------------------------------------------------------------------------
// 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;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
using VerifyCS = CSharpSourceGeneratorVerifier<ActorClientGenerator>;
public sealed class ActorClientGeneratorTests
{
private const string ActorMethodAttributeText = $@"// <auto-generated/>
#nullable enable
using System;
namespace Dapr.Actors.Generators
{{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
internal sealed class ActorMethodAttribute : Attribute
{{
public string? Name {{ get; set; }}
}}
}}";
private static readonly (string, SourceText) ActorMethodAttributeSource = (
Path.Combine("Dapr.Actors.Generators", "Dapr.Actors.Generators.ActorClientGenerator", "Dapr.Actors.Generators.ActorMethodAttribute.g.cs"),
SourceText.From(ActorMethodAttributeText, Encoding.UTF8));
private const string GenerateActorClientAttributeText = $@"// <auto-generated/>
#nullable enable
using System;
namespace Dapr.Actors.Generators
{{
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
internal sealed class GenerateActorClientAttribute : Attribute
{{
public string? Name {{ get; set; }}
public string? Namespace {{ get; set; }}
}}
}}";
private static readonly (string, SourceText) GenerateActorClientAttributeSource = (
Path.Combine("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 = null, string? generatedSource = null)
{
var test = new VerifyCS.Test
{
TestState =
{
AdditionalReferences = { AdditionalMetadataReferences.Actors },
Sources = { originalSource },
GeneratedSources =
{
ActorMethodAttributeSource,
GenerateActorClientAttributeSource
},
}
};
if (generatedName is not null && generatedSource is not null)
{
test.TestState.GeneratedSources.Add((
Path.Combine("Dapr.Actors.Generators", "Dapr.Actors.Generators.ActorClientGenerator", generatedName),
SourceText.From(generatedSource, Encoding.UTF8)));
}
return test;
}
[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 = @"// <auto-generated/>
#nullable enable
namespace Test
{
public sealed class TestActorClient : Test.ITestActor
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(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 TestInternalInterface()
{
var originalSource = @"
using Dapr.Actors.Generators;
using System.Threading.Tasks;
namespace Test
{
[GenerateActorClient]
internal interface ITestActor
{
Task TestMethod();
}
}";
var generatedSource = @"// <auto-generated/>
#nullable enable
namespace Test
{
internal sealed class TestActorClient : Test.ITestActor
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(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 TestSingleGenericInternalInterface()
{
var originalSource = @"
using Dapr.Actors.Generators;
using System.Threading.Tasks;
namespace Test
{
[GenerateActorClient]
internal interface ITestActor<TGenericType>
{
Task TestMethod();
}
}";
var generatedSource = @"// <auto-generated/>
#nullable enable
namespace Test
{
internal sealed class TestActorClient<TGenericType> : Test.ITestActor<TGenericType>
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(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 TestMultipleGenericsInternalInterface()
{
var originalSource = @"
using Dapr.Actors.Generators;
using System.Threading.Tasks;
namespace Test
{
[GenerateActorClient]
internal interface ITestActor<TGenericType1, TGenericType2>
{
Task TestMethod();
}
}";
var generatedSource = @"// <auto-generated/>
#nullable enable
namespace Test
{
internal sealed class TestActorClient<TGenericType1, TGenericType2> : Test.ITestActor<TGenericType1, TGenericType2>
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(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 = @"// <auto-generated/>
#nullable enable
namespace Test
{
internal sealed class MyTestActorClient : Test.ITestActor
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public MyTestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(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 TestSingleGenericRenamedClient()
{
var originalSource = @"
using Dapr.Actors.Generators;
using System.Threading.Tasks;
namespace Test
{
[GenerateActorClient(Name = ""MyTestActorClient"")]
internal interface ITestActor<TGenericType>
{
Task TestMethod();
}
}";
var generatedSource = @"// <auto-generated/>
#nullable enable
namespace Test
{
internal sealed class MyTestActorClient<TGenericType> : Test.ITestActor<TGenericType>
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public MyTestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(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 TestMultipleGenericsRenamedClient()
{
var originalSource = @"
using Dapr.Actors.Generators;
using System.Threading.Tasks;
namespace Test
{
[GenerateActorClient(Name = ""MyTestActorClient"")]
internal interface ITestActor<TGenericType1, TGenericType2>
{
Task TestMethod();
}
}";
var generatedSource = @"// <auto-generated/>
#nullable enable
namespace Test
{
internal sealed class MyTestActorClient<TGenericType1, TGenericType2> : Test.ITestActor<TGenericType1, TGenericType2>
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public MyTestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(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 = @"// <auto-generated/>
#nullable enable
namespace MyTest
{
internal sealed class TestActorClient : Test.ITestActor
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(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 = @"// <auto-generated/>
#nullable enable
namespace Test
{
public sealed class TestActorClient : Test.ITestActor
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(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()
{
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 = @"// <auto-generated/>
#nullable enable
namespace Test
{
public sealed class TestActorClient : Test.ITestActor
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(actorProxy));
}
this.actorProxy = actorProxy;
}
public System.Threading.Tasks.Task TestMethod(Test.TestValue value)
{
return this.actorProxy.InvokeMethodAsync<Test.TestValue>(""TestMethod"", value);
}
}
}";
await CreateTest(originalSource, "Test.TestActorClient.g.cs", generatedSource).RunAsync();
}
[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<TestValue> TestMethodAsync();
}
}";
var generatedSource = @"// <auto-generated/>
#nullable enable
namespace Test
{
public sealed class TestActorClient : Test.ITestActor
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(actorProxy));
}
this.actorProxy = actorProxy;
}
public System.Threading.Tasks.Task<Test.TestValue> TestMethodAsync()
{
return this.actorProxy.InvokeMethodAsync<Test.TestValue>(""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<TestReturnValue> TestMethodAsync(TestRequestValue value);
}
}";
var generatedSource = @"// <auto-generated/>
#nullable enable
namespace Test
{
public sealed class TestActorClient : Test.ITestActor
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(actorProxy));
}
this.actorProxy = actorProxy;
}
public System.Threading.Tasks.Task<Test.TestReturnValue> TestMethodAsync(Test.TestRequestValue value)
{
return this.actorProxy.InvokeMethodAsync<Test.TestRequestValue, Test.TestReturnValue>(""TestMethodAsync"", value);
}
}
}";
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);
}
}";
var generatedSource = @"// <auto-generated/>
#nullable enable
namespace Test
{
public sealed class TestActorClient : Test.ITestActor
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(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 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 = @"// <auto-generated/>
#nullable enable
namespace Test
{
public sealed class TestActorClient : Test.ITestActor
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(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()
{
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);
}
}
";
var generatedSource = @"// <auto-generated/>
#nullable enable
namespace Test
{
public sealed class TestActorClient : Test.ITestActor
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(actorProxy));
}
this.actorProxy = actorProxy;
}
public System.Threading.Tasks.Task TestMethodAsync(Test.TestValue value, System.Threading.CancellationToken cancellationToken)
{
return this.actorProxy.InvokeMethodAsync<Test.TestValue>(""TestMethodAsync"", value, cancellationToken);
}
}
}";
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 = @"// <auto-generated/>
#nullable enable
namespace Test
{
public sealed class TestActorClient : Test.ITestActor
{
private readonly Dapr.Actors.Client.ActorProxy actorProxy;
public TestActorClient(Dapr.Actors.Client.ActorProxy actorProxy)
{
if (actorProxy is null)
{
throw new System.ArgumentNullException(nameof(actorProxy));
}
this.actorProxy = actorProxy;
}
public System.Threading.Tasks.Task TestMethodAsync(Test.TestValue value, System.Threading.CancellationToken cancellationToken = default)
{
return this.actorProxy.InvokeMethodAsync<Test.TestValue>(""TestMethodAsync"", value, cancellationToken);
}
}
}";
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()
{
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("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"));
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("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"));
await test.RunAsync();
}
}