-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
.Net: Shared (Cross-Runtime) integration tests for Processes (#9550)
### Motivation and Context This PR introduces integration tests for Processes that are shared across all runtimes. This will allow us to run the exact same tests using all supported runtimes. ### Description This PR creates a few new projects that are needed to share the integration tests across runtimes. - `Process.IntegrationTests.Resources` contains all the definitions or steps, state, processes, etc. that are used in the shared tests. - `Process.IntegrationTests.Shared` contains all of the shared tests. - `Process.IntegrationTestRunner.Local` is the test project that executes the shared tests against the Local runtime. - `Process.IntegrationTestRunner.Dapr` is the test project that executes the shared tests against the Dapr runtime. - `Process.IntegrationTestHost.Dapr` is the project that hosts Dapr tests in an ASP.NET Core Web API. The PR enables all of these tests to run locally, a follow up PR will configure these tests to run in the GitHub pipeline. ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄 --------- Co-authored-by: alliscode <[email protected]> Co-authored-by: Chris <[email protected]>
- Loading branch information
1 parent
7222246
commit c613ae4
Showing
34 changed files
with
1,382 additions
and
325 deletions.
There are no files selected for viewing
This file contains 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 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
30 changes: 30 additions & 0 deletions
30
dotnet/src/Experimental/Process.Abstractions/KernelProcessContext.cs
This file contains 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,30 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using System.Threading.Tasks; | ||
|
||
namespace Microsoft.SemanticKernel.Process; | ||
|
||
/// <summary> | ||
/// Represents the context of a running process. | ||
/// </summary> | ||
public abstract class KernelProcessContext | ||
{ | ||
/// <summary> | ||
/// Sends a message to the process. | ||
/// </summary> | ||
/// <param name="processEvent">The event to sent to the process.</param> | ||
/// <returns>A <see cref="Task"/></returns> | ||
public abstract Task SendEventAsync(KernelProcessEvent processEvent); | ||
|
||
/// <summary> | ||
/// Stops the process. | ||
/// </summary> | ||
/// <returns>A <see cref="Task"/></returns> | ||
public abstract Task StopAsync(); | ||
|
||
/// <summary> | ||
/// Gets a snapshot of the current state of the process. | ||
/// </summary> | ||
/// <returns>A <see cref="Task{T}"/> where T is <see cref="KernelProcess"/></returns> | ||
public abstract Task<KernelProcess> GetStateAsync(); | ||
} |
21 changes: 21 additions & 0 deletions
21
dotnet/src/Experimental/Process.IntegrationTestHost.Dapr/Contracts/ProcessStartRequest.cs
This file contains 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,21 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using Microsoft.SemanticKernel; | ||
|
||
namespace SemanticKernel.Process.IntegrationTests; | ||
|
||
/// <summary> | ||
/// Represents the body of a POST request to start a process in the test host. | ||
/// </summary> | ||
public record ProcessStartRequest | ||
{ | ||
/// <summary> | ||
/// The process to start. | ||
/// </summary> | ||
public required DaprProcessInfo Process { get; set; } | ||
|
||
/// <summary> | ||
/// The initial event to send to the process. | ||
/// </summary> | ||
public required KernelProcessEvent InitialEvent { get; set; } | ||
} |
89 changes: 89 additions & 0 deletions
89
...et/src/Experimental/Process.IntegrationTestHost.Dapr/Controllers/ProcessTestController.cs
This file contains 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,89 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using System.Text.Json; | ||
using Dapr.Actors.Client; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.SemanticKernel; | ||
|
||
namespace SemanticKernel.Process.IntegrationTests.Controllers; | ||
|
||
/// <summary> | ||
/// A controller for starting and managing processes. | ||
/// </summary> | ||
[ApiController] | ||
[Route("/")] | ||
[Produces("application/json")] | ||
public class ProcessTestController : Controller | ||
{ | ||
private static readonly Dictionary<string, DaprKernelProcessContext> s_processes = new(); | ||
private readonly Kernel _kernel; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ProcessTestController"/> class. | ||
/// </summary> | ||
/// <param name="kernel"></param> | ||
public ProcessTestController(Kernel kernel) | ||
{ | ||
this._kernel = kernel; | ||
} | ||
|
||
/// <summary> | ||
/// Starts a process. | ||
/// </summary> | ||
/// <param name="processId">The Id of the process</param> | ||
/// <param name="request">The request</param> | ||
/// <returns></returns> | ||
[HttpPost("processes/{processId}")] | ||
public async Task<IActionResult> StartProcessAsync(string processId, [FromBody] ProcessStartRequest request) | ||
{ | ||
if (s_processes.ContainsKey(processId)) | ||
{ | ||
return this.BadRequest("Process already started"); | ||
} | ||
|
||
if (request.InitialEvent?.Data is JsonElement jsonElement) | ||
{ | ||
object? data = jsonElement.Deserialize<string>(); | ||
request.InitialEvent = request.InitialEvent with { Data = data }; | ||
} | ||
|
||
var kernelProcess = request.Process.ToKernelProcess(); | ||
var context = await kernelProcess.StartAsync(request.InitialEvent!); | ||
s_processes.Add(processId, context); | ||
|
||
return this.Ok(); | ||
} | ||
|
||
/// <summary> | ||
/// Retrieves information about a process. | ||
/// </summary> | ||
/// <param name="processId">The Id of the process.</param> | ||
/// <returns></returns> | ||
[HttpGet("processes/{processId}")] | ||
public async Task<IActionResult> GetProcessAsync(string processId) | ||
{ | ||
if (!s_processes.TryGetValue(processId, out DaprKernelProcessContext? context)) | ||
{ | ||
return this.NotFound(); | ||
} | ||
|
||
var process = await context.GetStateAsync(); | ||
var daprProcess = DaprProcessInfo.FromKernelProcess(process); | ||
|
||
var serialized = JsonSerializer.Serialize(daprProcess); | ||
|
||
return this.Ok(daprProcess); | ||
} | ||
|
||
/// <summary> | ||
/// Checks the health of the Dapr runtime by attempting to send a message to a health actor. | ||
/// </summary> | ||
/// <returns></returns> | ||
[HttpGet("daprHealth")] | ||
public async Task<IActionResult> HealthCheckAsync() | ||
{ | ||
var healthActor = ActorProxy.Create<IHealthActor>(new Dapr.Actors.ActorId(Guid.NewGuid().ToString("n")), nameof(HealthActor)); | ||
await healthActor.HealthCheckAsync(); | ||
return this.Ok(); | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
dotnet/src/Experimental/Process.IntegrationTestHost.Dapr/HealthActor.cs
This file contains 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,25 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using Dapr.Actors.Runtime; | ||
|
||
namespace SemanticKernel.Process.IntegrationTests; | ||
|
||
/// <summary> | ||
/// An implementation of the health actor that is only used for testing the health of the Dapr runtime. | ||
/// </summary> | ||
public class HealthActor : Actor, IHealthActor | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="HealthActor"/> class. | ||
/// </summary> | ||
/// <param name="host"></param> | ||
public HealthActor(ActorHost host) : base(host) | ||
{ | ||
} | ||
|
||
/// <inheritdoc /> | ||
public Task HealthCheckAsync() | ||
{ | ||
return Task.CompletedTask; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
dotnet/src/Experimental/Process.IntegrationTestHost.Dapr/IHealthActor.cs
This file contains 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,17 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using Dapr.Actors; | ||
|
||
namespace SemanticKernel.Process.IntegrationTests; | ||
|
||
/// <summary> | ||
/// An interface for a health actor that is only used for testing the health of the Dapr runtime. | ||
/// </summary> | ||
public interface IHealthActor : IActor | ||
{ | ||
/// <summary> | ||
/// An empty method used to determine if Dapr runtime is up and reachable. | ||
/// </summary> | ||
/// <returns></returns> | ||
Task HealthCheckAsync(); | ||
} |
27 changes: 27 additions & 0 deletions
27
...src/Experimental/Process.IntegrationTestHost.Dapr/Process.IntegrationTestHost.Dapr.csproj
This file contains 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.Web"> | ||
|
||
<PropertyGroup> | ||
<RootNamespace>SemanticKernel.Process.IntegrationTests</RootNamespace> | ||
<AssemblyName>SemanticKernel.Process.IntegrationTestHost.Dapr</AssemblyName> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<IsPackable>false</IsPackable> | ||
<NoWarn>$(NoWarn);CA2007,CA1861,VSTHRD111,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0080,SKEXP0110</NoWarn> | ||
<UserSecretsId>b7762d10-e29b-4bb1-8b74-b6d69a667dd4</UserSecretsId> | ||
<IsTestProject>true</IsTestProject> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Dapr.Actors" /> | ||
<PackageReference Include="Dapr.Actors.AspNetCore" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Process.Abstractions\Process.Abstractions.csproj" /> | ||
<ProjectReference Include="..\Process.Core\Process.Core.csproj" /> | ||
<ProjectReference Include="..\Process.IntegrationTests.Resources\Process.IntegrationTests.Resources.csproj" /> | ||
<ProjectReference Include="..\Process.Runtime.Dapr\Process.Runtime.Dapr.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
102 changes: 102 additions & 0 deletions
102
dotnet/src/Experimental/Process.IntegrationTestHost.Dapr/ProcessStateTypeResolver.cs
This file contains 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,102 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
using System.Text.Json.Serialization.Metadata; | ||
using Microsoft.SemanticKernel; | ||
|
||
namespace SemanticKernel.Process.IntegrationTests; | ||
|
||
/// <summary> | ||
/// An implementation of <see cref="JsonTypeInfoResolver"/> that resolves the type information for <see cref="KernelProcessStepState{T}"/>. | ||
/// </summary> | ||
public class ProcessStateTypeResolver<T> : DefaultJsonTypeInfoResolver where T : KernelProcessStep | ||
{ | ||
private static readonly Type s_genericType = typeof(KernelProcessStep<>); | ||
private readonly Dictionary<string, Type> _types = new() { { "", typeof(KernelProcessState) } }; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ProcessStateTypeResolver{T}"/> class. | ||
/// </summary> | ||
public ProcessStateTypeResolver() | ||
{ | ||
// Load all types from the resources assembly that derive from KernelProcessStep | ||
var assembly = typeof(T).Assembly; | ||
var stepTypes = assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(KernelProcessStep))); | ||
|
||
foreach (var type in stepTypes) | ||
{ | ||
if (TryGetSubtypeOfStatefulStep(type, out Type? genericStepType) && genericStepType is not null) | ||
{ | ||
var userStateType = genericStepType.GetGenericArguments()[0]; | ||
var stateType = typeof(KernelProcessStepState<>).MakeGenericType(userStateType); | ||
this._types.TryAdd(userStateType.Name, stateType); | ||
} | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) | ||
{ | ||
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options); | ||
|
||
Type baseType = typeof(KernelProcessStepState); | ||
if (jsonTypeInfo.Type == baseType) | ||
{ | ||
var jsonDerivedTypes = this._types.Select(t => new JsonDerivedType(t.Value, t.Key)).ToList(); | ||
|
||
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions | ||
{ | ||
TypeDiscriminatorPropertyName = "$state-type", | ||
IgnoreUnrecognizedTypeDiscriminators = true, | ||
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization | ||
}; | ||
|
||
// Add the known derived types to the collection | ||
var derivedTypesCollection = jsonTypeInfo.PolymorphismOptions.DerivedTypes; | ||
if (derivedTypesCollection is List<JsonDerivedType> list) | ||
{ | ||
list.AddRange(jsonDerivedTypes); | ||
} | ||
else | ||
{ | ||
foreach (var item in jsonDerivedTypes!) | ||
{ | ||
derivedTypesCollection!.Add(item); | ||
} | ||
} | ||
} | ||
else if (jsonTypeInfo.Type == typeof(DaprStepInfo)) | ||
{ | ||
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions | ||
{ | ||
TypeDiscriminatorPropertyName = "$state-type", | ||
IgnoreUnrecognizedTypeDiscriminators = true, | ||
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization, | ||
DerivedTypes = | ||
{ | ||
new JsonDerivedType(typeof(DaprProcessInfo), nameof(DaprProcessInfo)) | ||
} | ||
}; | ||
} | ||
|
||
return jsonTypeInfo; | ||
} | ||
|
||
private static bool TryGetSubtypeOfStatefulStep(Type? type, out Type? genericStateType) | ||
{ | ||
while (type != null && type != typeof(object)) | ||
{ | ||
if (type.IsGenericType && type.GetGenericTypeDefinition() == s_genericType) | ||
{ | ||
genericStateType = type; | ||
return true; | ||
} | ||
|
||
type = type.BaseType; | ||
} | ||
|
||
genericStateType = null; | ||
return false; | ||
} | ||
} |
Oops, something went wrong.