-
Notifications
You must be signed in to change notification settings - Fork 58
Add API for In-process Testing and Add Class-Syntax Integration Tests #476
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
469ed86
initial commit
nytian f3afc9d
Merge branch 'main' into nytian/class-syntax-test
nytian 3c5fdce
Merge branch 'main' into nytian/class-syntax-test
nytian 80129f8
udpate
nytian 4968924
add xml to remove warnings
nytian 3eaa5aa
Add xml
nytian 0eb5897
continue fix warnings
nytian 87a9d06
ignore warnings
nytian 62e97c5
update by comment
nytian 5806794
update
nytian 37b9970
Merge branch 'main' into nytian/class-syntax-test
nytian a21b586
merge latest from main
nytian 06300e4
Merge branch 'nytian/class-syntax-test' of https://github.com/microso…
nytian 2d1f0c2
reword comment
nytian cc9709d
Merge branch 'main' into nytian/class-syntax-test
nytian 9ab8eb8
Merge branch 'main' into nytian/class-syntax-test
nytian b5ed1e7
update by comment
nytian File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using DurableTask.Core; | ||
| using Grpc.Net.Client; | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.AspNetCore.Hosting; | ||
| using Microsoft.AspNetCore.Server.Kestrel.Core; | ||
| using Microsoft.DurableTask.Client; | ||
| using Microsoft.DurableTask.Testing.Sidecar; | ||
| using Microsoft.DurableTask.Testing.Sidecar.Grpc; | ||
| using Microsoft.DurableTask.Worker; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Hosting; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace Microsoft.DurableTask.Testing; | ||
|
|
||
| /// <summary> | ||
| /// In-process test host for testing class-based durable task orchestrations and activities | ||
| /// without requiring any external backend (Azure Storage, SQL, etc). | ||
| /// </summary> | ||
| public sealed class DurableTaskTestHost : IAsyncDisposable | ||
| { | ||
| readonly IWebHost sidecarHost; | ||
| readonly IHost workerHost; | ||
| readonly GrpcChannel grpcChannel; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="DurableTaskTestHost"/> class. | ||
| /// </summary> | ||
| /// <param name="sidecarHost">The gRPC sidecar host.</param> | ||
| /// <param name="workerHost">The worker host.</param> | ||
| /// <param name="grpcChannel">The gRPC channel.</param> | ||
| /// <param name="client">The durable task client.</param> | ||
| public DurableTaskTestHost(IWebHost sidecarHost, IHost workerHost, GrpcChannel grpcChannel, DurableTaskClient client) | ||
| { | ||
| this.sidecarHost = sidecarHost; | ||
| this.workerHost = workerHost; | ||
| this.grpcChannel = grpcChannel; | ||
| this.Client = client; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the durable task client for scheduling and managing orchestrations. | ||
| /// </summary> | ||
| public DurableTaskClient Client { get; } | ||
|
|
||
| /// <summary> | ||
| /// Starts a new in-process test host with the specified orchestrators and activities. | ||
| /// </summary> | ||
| /// <param name="registry">Action to configure the task registry by adding orchestrators and activities.</param> | ||
| /// <param name="options">Optional configuration options.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>A running test host ready to execute orchestrations.</returns> | ||
| public static async Task<DurableTaskTestHost> StartAsync( | ||
| Action<DurableTaskRegistry> registry, | ||
| DurableTaskTestHostOptions? options = null, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| options ??= new DurableTaskTestHostOptions(); | ||
|
|
||
| // Create in-memory orchestration service | ||
| var orchestrationService = new InMemoryOrchestrationService(options.LoggerFactory); | ||
|
|
||
| // Start gRPC sidecar server in-process | ||
| string address = options.Port.HasValue | ||
| ? $"http://localhost:{options.Port.Value}" | ||
| : $"http://localhost:{Random.Shared.Next(30000, 40000)}"; | ||
|
|
||
| var sidecarHost = new WebHostBuilder() | ||
| .UseKestrel(kestrelOptions => | ||
| { | ||
| // Configure for HTTP/2 (required for gRPC) | ||
| kestrelOptions.ConfigureEndpointDefaults(listenOptions => | ||
| listenOptions.Protocols = HttpProtocols.Http2); | ||
| }) | ||
| .UseUrls(address) | ||
| .ConfigureServices(services => | ||
| { | ||
| services.AddGrpc(); | ||
| services.AddSingleton<IOrchestrationService>(orchestrationService); | ||
| services.AddSingleton<IOrchestrationServiceClient>(orchestrationService); | ||
| services.AddSingleton<TaskHubGrpcServer>(); | ||
| }) | ||
| .Configure(app => | ||
| { | ||
| app.UseRouting(); | ||
| app.UseEndpoints(endpoints => | ||
| { | ||
| endpoints.MapGrpcService<TaskHubGrpcServer>(); | ||
| }); | ||
| }) | ||
| .Build(); | ||
|
|
||
| sidecarHost.Start(); | ||
| var grpcChannel = GrpcChannel.ForAddress(address); | ||
|
|
||
| // Create worker host with user's orchestrators and activities | ||
| var workerHost = Host.CreateDefaultBuilder() | ||
| .ConfigureLogging(logging => | ||
| { | ||
| logging.ClearProviders(); | ||
| if (options.LoggerFactory != null) | ||
| { | ||
| logging.Services.AddSingleton(options.LoggerFactory); | ||
| } | ||
| }) | ||
| .ConfigureServices(services => | ||
| { | ||
| // Register worker that connects to our in-process sidecar | ||
| services.AddDurableTaskWorker(builder => | ||
| { | ||
| builder.UseGrpc(grpcChannel); | ||
| builder.AddTasks(registry); | ||
| }); | ||
|
|
||
| // Register client that connects to the same sidecar | ||
| services.AddDurableTaskClient(builder => | ||
| { | ||
| builder.UseGrpc(grpcChannel); | ||
| builder.RegisterDirectly(); | ||
| }); | ||
| }) | ||
| .Build(); | ||
|
|
||
| await workerHost.StartAsync(cancellationToken); | ||
|
|
||
| // Get the client from the worker host | ||
| var client = workerHost.Services.GetRequiredService<DurableTaskClient>(); | ||
|
|
||
| return new DurableTaskTestHost(sidecarHost, workerHost, grpcChannel, client); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Clean up all resources. | ||
| /// </summary> | ||
| /// <returns>A task representing the asynchronous dispose operation.</returns> | ||
| public async ValueTask DisposeAsync() | ||
| { | ||
| await this.workerHost.StopAsync(); | ||
| this.workerHost.Dispose(); | ||
|
|
||
| await this.grpcChannel.ShutdownAsync(); | ||
| this.grpcChannel.Dispose(); | ||
|
|
||
| await this.sidecarHost.StopAsync(); | ||
| this.sidecarHost.Dispose(); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Configuration options for <see cref="DurableTaskTestHost"/>. | ||
| /// </summary> | ||
| public class DurableTaskTestHostOptions | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the specific port to use for the gRPC sidecar. | ||
| /// If not set, a random port between 30000-40000 will be used. | ||
| /// </summary> | ||
| public int? Port { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets an optional logger factory for capturing logs during tests. | ||
| /// Null by default. | ||
| /// </summary> | ||
| public ILoggerFactory? LoggerFactory { get; set; } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net6.0</TargetFramework> | ||
| <RootNamespace>Microsoft.DurableTask.Testing</RootNamespace> | ||
| <AssemblyName>Microsoft.DurableTask.InProcessTestHost</AssemblyName> | ||
| <PackageId>Microsoft.DurableTask.InProcessTestHost</PackageId> | ||
|
nytian marked this conversation as resolved.
|
||
| <Version>0.1.0-preview.1</Version> | ||
|
|
||
| <!-- Suppress CA1848: Use LoggerMessage delegates for high-performance logging scenarios --> | ||
| <NoWarn>$(NoWarn);CA1848</NoWarn> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Azure.DurableTask.Core" /> | ||
| <PackageReference Include="Grpc.AspNetCore.Server" /> | ||
| <PackageReference Include="Grpc.Net.Client" /> | ||
| <PackageReference Include="Google.Protobuf" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="../Client/Grpc/Client.Grpc.csproj" /> | ||
| <ProjectReference Include="../Worker/Grpc/Worker.Grpc.csproj" /> | ||
| <ProjectReference Include="../Grpc/Grpc.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # DurableTaskTestHost - Testing Durable Orchestrations In-Process | ||
|
|
||
| `DurableTaskTestHost` is a simple API for testing your durable task orchestrations and activities **in-process** without requiring any external backend. | ||
|
|
||
| Supports both **class-based** and **function-based** syntax. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| 1. Configure options | ||
| ```csharp | ||
| var options = new DurableTaskTestHostOptions | ||
| { | ||
| Port = 31000, // Optional: specific port (random by default) | ||
| LoggerFactory = myLoggerFactory // Optional: pass logger factory for logging | ||
| }; | ||
|
|
||
| ``` | ||
|
|
||
| 2. Register test orchestrations and activities. | ||
|
|
||
| ```csharp | ||
| await using var testHost = await DurableTaskTestHost.StartAsync(registry => | ||
| { | ||
| // Class-based | ||
| registry.AddOrchestrator<MyOrchestrator>(); | ||
| registry.AddActivity<MyActivity>(); | ||
|
|
||
| // Function-based | ||
| registry.AddOrchestratorFunc("MyFunc", (ctx, input) => Task.FromResult("done")); | ||
| registry.AddActivityFunc("MyActivity", (ctx, input) => Task.FromResult("result")); | ||
| }); | ||
|
|
||
| ``` | ||
|
|
||
| 3. Test | ||
| ```csharp | ||
| string instanceId = await testHost.Client.ScheduleNewOrchestrationInstanceAsync("MyOrchestrator"); | ||
| var result = await testHost.Client.WaitForInstanceCompletionAsync(instanceId); | ||
| ``` | ||
| . | ||
| ## More Samples | ||
|
|
||
| See [BasicOrchestrationTests.cs](../../test/InProcessTestHost.Tests/BasicOrchestrationTests.cs) for complete samples showing both class-syntax and function-syntax orchestrations. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
src/InProcessTestHost/Sidecar/Dispatcher/GrpcOrchestratorExecutionResult.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using DurableTask.Core; | ||
|
|
||
| namespace Microsoft.DurableTask.Testing.Sidecar.Dispatcher; | ||
|
|
||
| /// <summary> | ||
| /// Grpc orchestration execution result. | ||
| /// </summary> | ||
| public class GrpcOrchestratorExecutionResult : OrchestratorExecutionResult | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the orcehstration activity spanId. | ||
| /// </summary> | ||
| public string? OrchestrationActivitySpanId { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the orchestration activity start time. | ||
| /// </summary> | ||
| public DateTimeOffset? OrchestrationActivityStartTime { get; set; } | ||
| } |
18 changes: 18 additions & 0 deletions
18
src/InProcessTestHost/Sidecar/Dispatcher/GrpcScheduleTaskOrchestratorAction.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using DurableTask.Core.Command; | ||
| using DurableTask.Core.Tracing; | ||
|
|
||
| namespace Microsoft.DurableTask.Testing.Sidecar.Dispatcher; | ||
|
|
||
| /// <summary> | ||
| /// gRPC-specific implementation of ScheduleTaskOrchestratorAction that includes distributed tracing context. | ||
| /// </summary> | ||
| public class GrpcScheduleTaskOrchestratorAction : ScheduleTaskOrchestratorAction | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the parent trace context for distributed tracing. | ||
| /// </summary> | ||
| public DistributedTraceContext? ParentTraceContext { get; set; } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know some of our packages support
netstandard2.0andnet6. Do we need to do that here as well or are we OK with justnet6?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just tried, we are using Grpc.AspNetCore.Server in this test pkg which doesn't support netstandard2.0. I guess we are fine? If there are special customer asking for that then I can move back to add the TFM. Now it's just preview pkg