Skip to content

Commit c613ae4

Browse files
alliscodealliscodecrickman
authored
.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]>
1 parent 7222246 commit c613ae4

34 files changed

+1382
-325
lines changed

dotnet/Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
<PackageVersion Include="Dapr.Actors" Version="1.14.0" />
1313
<PackageVersion Include="Dapr.Actors.AspNetCore" Version="1.14.0" />
1414
<PackageVersion Include="Dapr.AspNetCore" Version="1.14.0" />
15+
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" />
16+
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
1517
<PackageVersion Include="Microsoft.VisualStudio.Threading" Version="17.11.20" />
1618
<PackageVersion Include="MSTest.TestFramework" Version="3.6.1" />
1719
<PackageVersion Include="OpenAI" Version="[2.1.0-beta.1]" />

dotnet/SK-dotnet.sln

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStartedWithVectorSto
412412
EndProject
413413
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.Runtime.Dapr.UnitTests", "src\Experimental\Process.Runtime.Dapr.UnitTests\Process.Runtime.Dapr.UnitTests.csproj", "{DB58FDD0-308E-472F-BFF5-508BC64C727E}"
414414
EndProject
415+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.IntegrationTestRunner.Dapr", "src\Experimental\Process.IntegrationTestRunner.Dapr\Process.IntegrationTestRunner.Dapr.csproj", "{F9C9CBD7-0DBF-47BC-B22E-2FB6D08FF858}"
416+
EndProject
417+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.IntegrationTests.Shared", "src\Experimental\Process.IntegrationTests.Shared\Process.IntegrationTests.Shared.csproj", "{24CFE182-3342-4EB6-B0A7-91B211454BE9}"
418+
EndProject
419+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.IntegrationTestRunner.Local", "src\Experimental\Process.IntegrationTestRunner.Local\Process.IntegrationTestRunner.Local.csproj", "{BB358B41-6F95-4513-BFA4-6F026005B5AB}"
420+
EndProject
421+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.IntegrationTestHost.Dapr", "src\Experimental\Process.IntegrationTestHost.Dapr\Process.IntegrationTestHost.Dapr.csproj", "{A2D349C4-EA6E-465C-B86D-00C2942E3135}"
422+
EndProject
423+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Process.IntegrationTests.Resources", "src\Experimental\Process.IntegrationTests.Resources\Process.IntegrationTests.Resources.csproj", "{B35B1DEB-04DF-4141-9163-01031B22C5D1}"
424+
EndProject
415425
Global
416426
GlobalSection(SolutionConfigurationPlatforms) = preSolution
417427
Debug|Any CPU = Debug|Any CPU
@@ -1085,6 +1095,36 @@ Global
10851095
{DB58FDD0-308E-472F-BFF5-508BC64C727E}.Publish|Any CPU.Build.0 = Debug|Any CPU
10861096
{DB58FDD0-308E-472F-BFF5-508BC64C727E}.Release|Any CPU.ActiveCfg = Release|Any CPU
10871097
{DB58FDD0-308E-472F-BFF5-508BC64C727E}.Release|Any CPU.Build.0 = Release|Any CPU
1098+
{F9C9CBD7-0DBF-47BC-B22E-2FB6D08FF858}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1099+
{F9C9CBD7-0DBF-47BC-B22E-2FB6D08FF858}.Debug|Any CPU.Build.0 = Debug|Any CPU
1100+
{F9C9CBD7-0DBF-47BC-B22E-2FB6D08FF858}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
1101+
{F9C9CBD7-0DBF-47BC-B22E-2FB6D08FF858}.Publish|Any CPU.Build.0 = Debug|Any CPU
1102+
{F9C9CBD7-0DBF-47BC-B22E-2FB6D08FF858}.Release|Any CPU.ActiveCfg = Release|Any CPU
1103+
{F9C9CBD7-0DBF-47BC-B22E-2FB6D08FF858}.Release|Any CPU.Build.0 = Release|Any CPU
1104+
{24CFE182-3342-4EB6-B0A7-91B211454BE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1105+
{24CFE182-3342-4EB6-B0A7-91B211454BE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
1106+
{24CFE182-3342-4EB6-B0A7-91B211454BE9}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
1107+
{24CFE182-3342-4EB6-B0A7-91B211454BE9}.Publish|Any CPU.Build.0 = Debug|Any CPU
1108+
{24CFE182-3342-4EB6-B0A7-91B211454BE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
1109+
{24CFE182-3342-4EB6-B0A7-91B211454BE9}.Release|Any CPU.Build.0 = Release|Any CPU
1110+
{BB358B41-6F95-4513-BFA4-6F026005B5AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1111+
{BB358B41-6F95-4513-BFA4-6F026005B5AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
1112+
{BB358B41-6F95-4513-BFA4-6F026005B5AB}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
1113+
{BB358B41-6F95-4513-BFA4-6F026005B5AB}.Publish|Any CPU.Build.0 = Debug|Any CPU
1114+
{BB358B41-6F95-4513-BFA4-6F026005B5AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
1115+
{BB358B41-6F95-4513-BFA4-6F026005B5AB}.Release|Any CPU.Build.0 = Release|Any CPU
1116+
{A2D349C4-EA6E-465C-B86D-00C2942E3135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1117+
{A2D349C4-EA6E-465C-B86D-00C2942E3135}.Debug|Any CPU.Build.0 = Debug|Any CPU
1118+
{A2D349C4-EA6E-465C-B86D-00C2942E3135}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
1119+
{A2D349C4-EA6E-465C-B86D-00C2942E3135}.Publish|Any CPU.Build.0 = Debug|Any CPU
1120+
{A2D349C4-EA6E-465C-B86D-00C2942E3135}.Release|Any CPU.ActiveCfg = Release|Any CPU
1121+
{A2D349C4-EA6E-465C-B86D-00C2942E3135}.Release|Any CPU.Build.0 = Release|Any CPU
1122+
{B35B1DEB-04DF-4141-9163-01031B22C5D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1123+
{B35B1DEB-04DF-4141-9163-01031B22C5D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
1124+
{B35B1DEB-04DF-4141-9163-01031B22C5D1}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
1125+
{B35B1DEB-04DF-4141-9163-01031B22C5D1}.Publish|Any CPU.Build.0 = Debug|Any CPU
1126+
{B35B1DEB-04DF-4141-9163-01031B22C5D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
1127+
{B35B1DEB-04DF-4141-9163-01031B22C5D1}.Release|Any CPU.Build.0 = Release|Any CPU
10881128
EndGlobalSection
10891129
GlobalSection(SolutionProperties) = preSolution
10901130
HideSolutionNode = FALSE
@@ -1234,6 +1274,11 @@ Global
12341274
{DAC54048-A39A-4739-8307-EA5A291F2EA0} = {0D8C6358-5DAA-4EA6-A924-C268A9A21BC9}
12351275
{8C3DE41C-E2C8-42B9-8638-574F8946EB0E} = {FA3720F1-C99A-49B2-9577-A940257098BF}
12361276
{DB58FDD0-308E-472F-BFF5-508BC64C727E} = {0D8C6358-5DAA-4EA6-A924-C268A9A21BC9}
1277+
{F9C9CBD7-0DBF-47BC-B22E-2FB6D08FF858} = {0D8C6358-5DAA-4EA6-A924-C268A9A21BC9}
1278+
{24CFE182-3342-4EB6-B0A7-91B211454BE9} = {0D8C6358-5DAA-4EA6-A924-C268A9A21BC9}
1279+
{BB358B41-6F95-4513-BFA4-6F026005B5AB} = {0D8C6358-5DAA-4EA6-A924-C268A9A21BC9}
1280+
{A2D349C4-EA6E-465C-B86D-00C2942E3135} = {0D8C6358-5DAA-4EA6-A924-C268A9A21BC9}
1281+
{B35B1DEB-04DF-4141-9163-01031B22C5D1} = {0D8C6358-5DAA-4EA6-A924-C268A9A21BC9}
12371282
EndGlobalSection
12381283
GlobalSection(ExtensibilityGlobals) = postSolution
12391284
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.Threading.Tasks;
4+
5+
namespace Microsoft.SemanticKernel.Process;
6+
7+
/// <summary>
8+
/// Represents the context of a running process.
9+
/// </summary>
10+
public abstract class KernelProcessContext
11+
{
12+
/// <summary>
13+
/// Sends a message to the process.
14+
/// </summary>
15+
/// <param name="processEvent">The event to sent to the process.</param>
16+
/// <returns>A <see cref="Task"/></returns>
17+
public abstract Task SendEventAsync(KernelProcessEvent processEvent);
18+
19+
/// <summary>
20+
/// Stops the process.
21+
/// </summary>
22+
/// <returns>A <see cref="Task"/></returns>
23+
public abstract Task StopAsync();
24+
25+
/// <summary>
26+
/// Gets a snapshot of the current state of the process.
27+
/// </summary>
28+
/// <returns>A <see cref="Task{T}"/> where T is <see cref="KernelProcess"/></returns>
29+
public abstract Task<KernelProcess> GetStateAsync();
30+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.SemanticKernel;
4+
5+
namespace SemanticKernel.Process.IntegrationTests;
6+
7+
/// <summary>
8+
/// Represents the body of a POST request to start a process in the test host.
9+
/// </summary>
10+
public record ProcessStartRequest
11+
{
12+
/// <summary>
13+
/// The process to start.
14+
/// </summary>
15+
public required DaprProcessInfo Process { get; set; }
16+
17+
/// <summary>
18+
/// The initial event to send to the process.
19+
/// </summary>
20+
public required KernelProcessEvent InitialEvent { get; set; }
21+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.Text.Json;
4+
using Dapr.Actors.Client;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.SemanticKernel;
7+
8+
namespace SemanticKernel.Process.IntegrationTests.Controllers;
9+
10+
/// <summary>
11+
/// A controller for starting and managing processes.
12+
/// </summary>
13+
[ApiController]
14+
[Route("/")]
15+
[Produces("application/json")]
16+
public class ProcessTestController : Controller
17+
{
18+
private static readonly Dictionary<string, DaprKernelProcessContext> s_processes = new();
19+
private readonly Kernel _kernel;
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="ProcessTestController"/> class.
23+
/// </summary>
24+
/// <param name="kernel"></param>
25+
public ProcessTestController(Kernel kernel)
26+
{
27+
this._kernel = kernel;
28+
}
29+
30+
/// <summary>
31+
/// Starts a process.
32+
/// </summary>
33+
/// <param name="processId">The Id of the process</param>
34+
/// <param name="request">The request</param>
35+
/// <returns></returns>
36+
[HttpPost("processes/{processId}")]
37+
public async Task<IActionResult> StartProcessAsync(string processId, [FromBody] ProcessStartRequest request)
38+
{
39+
if (s_processes.ContainsKey(processId))
40+
{
41+
return this.BadRequest("Process already started");
42+
}
43+
44+
if (request.InitialEvent?.Data is JsonElement jsonElement)
45+
{
46+
object? data = jsonElement.Deserialize<string>();
47+
request.InitialEvent = request.InitialEvent with { Data = data };
48+
}
49+
50+
var kernelProcess = request.Process.ToKernelProcess();
51+
var context = await kernelProcess.StartAsync(request.InitialEvent!);
52+
s_processes.Add(processId, context);
53+
54+
return this.Ok();
55+
}
56+
57+
/// <summary>
58+
/// Retrieves information about a process.
59+
/// </summary>
60+
/// <param name="processId">The Id of the process.</param>
61+
/// <returns></returns>
62+
[HttpGet("processes/{processId}")]
63+
public async Task<IActionResult> GetProcessAsync(string processId)
64+
{
65+
if (!s_processes.TryGetValue(processId, out DaprKernelProcessContext? context))
66+
{
67+
return this.NotFound();
68+
}
69+
70+
var process = await context.GetStateAsync();
71+
var daprProcess = DaprProcessInfo.FromKernelProcess(process);
72+
73+
var serialized = JsonSerializer.Serialize(daprProcess);
74+
75+
return this.Ok(daprProcess);
76+
}
77+
78+
/// <summary>
79+
/// Checks the health of the Dapr runtime by attempting to send a message to a health actor.
80+
/// </summary>
81+
/// <returns></returns>
82+
[HttpGet("daprHealth")]
83+
public async Task<IActionResult> HealthCheckAsync()
84+
{
85+
var healthActor = ActorProxy.Create<IHealthActor>(new Dapr.Actors.ActorId(Guid.NewGuid().ToString("n")), nameof(HealthActor));
86+
await healthActor.HealthCheckAsync();
87+
return this.Ok();
88+
}
89+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Dapr.Actors.Runtime;
4+
5+
namespace SemanticKernel.Process.IntegrationTests;
6+
7+
/// <summary>
8+
/// An implementation of the health actor that is only used for testing the health of the Dapr runtime.
9+
/// </summary>
10+
public class HealthActor : Actor, IHealthActor
11+
{
12+
/// <summary>
13+
/// Initializes a new instance of the <see cref="HealthActor"/> class.
14+
/// </summary>
15+
/// <param name="host"></param>
16+
public HealthActor(ActorHost host) : base(host)
17+
{
18+
}
19+
20+
/// <inheritdoc />
21+
public Task HealthCheckAsync()
22+
{
23+
return Task.CompletedTask;
24+
}
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Dapr.Actors;
4+
5+
namespace SemanticKernel.Process.IntegrationTests;
6+
7+
/// <summary>
8+
/// An interface for a health actor that is only used for testing the health of the Dapr runtime.
9+
/// </summary>
10+
public interface IHealthActor : IActor
11+
{
12+
/// <summary>
13+
/// An empty method used to determine if Dapr runtime is up and reachable.
14+
/// </summary>
15+
/// <returns></returns>
16+
Task HealthCheckAsync();
17+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<RootNamespace>SemanticKernel.Process.IntegrationTests</RootNamespace>
5+
<AssemblyName>SemanticKernel.Process.IntegrationTestHost.Dapr</AssemblyName>
6+
<TargetFramework>net8.0</TargetFramework>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<Nullable>enable</Nullable>
9+
<IsPackable>false</IsPackable>
10+
<NoWarn>$(NoWarn);CA2007,CA1861,VSTHRD111,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0080,SKEXP0110</NoWarn>
11+
<UserSecretsId>b7762d10-e29b-4bb1-8b74-b6d69a667dd4</UserSecretsId>
12+
<IsTestProject>true</IsTestProject>
13+
</PropertyGroup>
14+
15+
<ItemGroup>
16+
<PackageReference Include="Dapr.Actors" />
17+
<PackageReference Include="Dapr.Actors.AspNetCore" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<ProjectReference Include="..\Process.Abstractions\Process.Abstractions.csproj" />
22+
<ProjectReference Include="..\Process.Core\Process.Core.csproj" />
23+
<ProjectReference Include="..\Process.IntegrationTests.Resources\Process.IntegrationTests.Resources.csproj" />
24+
<ProjectReference Include="..\Process.Runtime.Dapr\Process.Runtime.Dapr.csproj" />
25+
</ItemGroup>
26+
27+
</Project>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.Text.Json;
4+
using System.Text.Json.Serialization;
5+
using System.Text.Json.Serialization.Metadata;
6+
using Microsoft.SemanticKernel;
7+
8+
namespace SemanticKernel.Process.IntegrationTests;
9+
10+
/// <summary>
11+
/// An implementation of <see cref="JsonTypeInfoResolver"/> that resolves the type information for <see cref="KernelProcessStepState{T}"/>.
12+
/// </summary>
13+
public class ProcessStateTypeResolver<T> : DefaultJsonTypeInfoResolver where T : KernelProcessStep
14+
{
15+
private static readonly Type s_genericType = typeof(KernelProcessStep<>);
16+
private readonly Dictionary<string, Type> _types = new() { { "", typeof(KernelProcessState) } };
17+
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="ProcessStateTypeResolver{T}"/> class.
20+
/// </summary>
21+
public ProcessStateTypeResolver()
22+
{
23+
// Load all types from the resources assembly that derive from KernelProcessStep
24+
var assembly = typeof(T).Assembly;
25+
var stepTypes = assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(KernelProcessStep)));
26+
27+
foreach (var type in stepTypes)
28+
{
29+
if (TryGetSubtypeOfStatefulStep(type, out Type? genericStepType) && genericStepType is not null)
30+
{
31+
var userStateType = genericStepType.GetGenericArguments()[0];
32+
var stateType = typeof(KernelProcessStepState<>).MakeGenericType(userStateType);
33+
this._types.TryAdd(userStateType.Name, stateType);
34+
}
35+
}
36+
}
37+
38+
/// <inheritdoc />
39+
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
40+
{
41+
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);
42+
43+
Type baseType = typeof(KernelProcessStepState);
44+
if (jsonTypeInfo.Type == baseType)
45+
{
46+
var jsonDerivedTypes = this._types.Select(t => new JsonDerivedType(t.Value, t.Key)).ToList();
47+
48+
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions
49+
{
50+
TypeDiscriminatorPropertyName = "$state-type",
51+
IgnoreUnrecognizedTypeDiscriminators = true,
52+
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization
53+
};
54+
55+
// Add the known derived types to the collection
56+
var derivedTypesCollection = jsonTypeInfo.PolymorphismOptions.DerivedTypes;
57+
if (derivedTypesCollection is List<JsonDerivedType> list)
58+
{
59+
list.AddRange(jsonDerivedTypes);
60+
}
61+
else
62+
{
63+
foreach (var item in jsonDerivedTypes!)
64+
{
65+
derivedTypesCollection!.Add(item);
66+
}
67+
}
68+
}
69+
else if (jsonTypeInfo.Type == typeof(DaprStepInfo))
70+
{
71+
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions
72+
{
73+
TypeDiscriminatorPropertyName = "$state-type",
74+
IgnoreUnrecognizedTypeDiscriminators = true,
75+
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
76+
DerivedTypes =
77+
{
78+
new JsonDerivedType(typeof(DaprProcessInfo), nameof(DaprProcessInfo))
79+
}
80+
};
81+
}
82+
83+
return jsonTypeInfo;
84+
}
85+
86+
private static bool TryGetSubtypeOfStatefulStep(Type? type, out Type? genericStateType)
87+
{
88+
while (type != null && type != typeof(object))
89+
{
90+
if (type.IsGenericType && type.GetGenericTypeDefinition() == s_genericType)
91+
{
92+
genericStateType = type;
93+
return true;
94+
}
95+
96+
type = type.BaseType;
97+
}
98+
99+
genericStateType = null;
100+
return false;
101+
}
102+
}

0 commit comments

Comments
 (0)