Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Microsoft.DurableTask.sln
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.IntegrationTests", "test\Grpc.IntegrationTests\Grpc.IntegrationTests.csproj", "{7825CFEA-2923-4C44-BA36-8E16259B9777}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{EFF7632B-821E-4CFC-B4A0-ED4B24296B17}"
ProjectSection(SolutionItems) = preProject
Directory.Packages.props = Directory.Packages.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsApp", "samples\AzureFunctionsApp\AzureFunctionsApp.csproj", "{848FC5BD-4A99-4A0D-9099-9597700AA7BC}"
EndProject
Expand Down Expand Up @@ -101,6 +104,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InProcessTestHost", "src\In
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InProcessTestHost.Tests", "test\InProcessTestHost.Tests\InProcessTestHost.Tests.csproj", "{B894780C-338F-475E-8E84-56AFA8197A06}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DtsPortableSdkEntityTests", "samples\DtsPortableSdkEntityTests\DtsPortableSdkEntityTests.csproj", "{B2BAE32E-C558-BB99-DC82-282613525497}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -271,6 +276,10 @@ Global
{FE1DA748-D6DB-E168-BC42-6DBBCEAF229C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE1DA748-D6DB-E168-BC42-6DBBCEAF229C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE1DA748-D6DB-E168-BC42-6DBBCEAF229C}.Release|Any CPU.Build.0 = Release|Any CPU
{B2BAE32E-C558-BB99-DC82-282613525497}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B2BAE32E-C558-BB99-DC82-282613525497}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2BAE32E-C558-BB99-DC82-282613525497}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2BAE32E-C558-BB99-DC82-282613525497}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -321,6 +330,7 @@ Global
{B894780C-338F-475E-8E84-56AFA8197A06} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
{6EB9D002-62C8-D6C1-62A8-14C54CA6DBBC} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
{FE1DA748-D6DB-E168-BC42-6DBBCEAF229C} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
{B2BAE32E-C558-BB99-DC82-282613525497} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
Expand Down
23 changes: 23 additions & 0 deletions samples/DtsPortableSdkEntityTests/DtsPortableSdkEntityTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="xunit" />
</ItemGroup>

<PropertyGroup>
<!-- This is not a unit test project, but a standalone test app, so it should not be run with dotnet test. -->
<IsTestProject>false</IsTestProject>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Client\AzureManaged\Client.AzureManaged.csproj" />
<ProjectReference Include="..\..\src\Worker\AzureManaged\Worker.AzureManaged.csproj" />
</ItemGroup>

</Project>
71 changes: 71 additions & 0 deletions samples/DtsPortableSdkEntityTests/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.Json.Serialization;
using Azure.Core;
using Azure.Identity;
using DtsPortableSdkEntityTests;
using DurableTask.Core.Entities;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker;
using Microsoft.DurableTask.Worker.AzureManaged;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

string connectionString = builder.Configuration["DTS_CONNECTION_STRING"] ??
// By default, use the connection string for the local development emulator
"Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";

// Add all the generated orchestrations and activities automatically
builder.Services.AddDurableTaskWorker(builder =>
{
builder.AddTasks(r =>
{
// TODO consider using source generator

// register all orchestrations and activities used in the tests
HashSet<Type> registeredTestTypes = [];
foreach(var test in All.GetAllTests())
{
if (!registeredTestTypes.Contains(test.GetType()))
{
test.Register(r, builder.Services);
registeredTestTypes.Add(test.GetType());
}
}

// register all entities
BatchEntity.Register(r);
Counter.Register(r);
FaultyEntity.Register(r);
Launcher.Register(r);
Relay.Register(r);
SchedulerEntity.Register(r);
SelfSchedulingEntity.Register(r);
StringStore.Register(r);
StringStore2.Register(r);
StringStore3.Register(r);
});

builder.UseDurableTaskScheduler(connectionString);
});

// Register the client, which can be used to start orchestrations
builder.Services.AddDurableTaskClient(builder =>
{
builder.UseDurableTaskScheduler(connectionString);
});

// Configure the HTTP request pipeline
builder.Services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});

// The actual listen URL can be configured in environment variables named "ASPNETCORE_URLS" or "ASPNETCORE_URLS_HTTPS"
WebApplication app = builder.Build();
app.MapControllers();
app.Run();
23 changes: 23 additions & 0 deletions samples/DtsPortableSdkEntityTests/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5203",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7225;http://localhost:5203",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions samples/DtsPortableSdkEntityTests/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
73 changes: 73 additions & 0 deletions samples/DtsPortableSdkEntityTests/common/ProblematicObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Azure.Core.Serialization;

namespace DtsPortableSdkEntityTests
{
internal static class CustomSerialization
{
public static ProblematicObject CreateUnserializableObject()
{
return new ProblematicObject(serializable: false, deserializable: false);
}

public static ProblematicObject CreateUndeserializableObject()
{
return new ProblematicObject(serializable: true, deserializable: false);
}

/// <summary>
/// An object for which we can inject errors on serialization or deserialization, to test
// how those are handled by the framework.
/// </summary>
public class ProblematicObject
{
public ProblematicObject(bool serializable = true, bool deserializable = true)
{
this.Serializable = serializable;
this.Deserializable = deserializable;
}

public bool Serializable { get; set; }

public bool Deserializable { get; set; }
}

public class ProblematicObjectJsonConverter : JsonConverter<ProblematicObject>
{
public override ProblematicObject Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
bool deserializable = reader.GetBoolean();
if (!deserializable)
{
throw new JsonException("problematic object: is not deserializable");
}
return new ProblematicObject(serializable: true, deserializable: true);
}

public override void Write(
Utf8JsonWriter writer,
ProblematicObject value,
JsonSerializerOptions options)
{
if (!value.Serializable)
{
throw new JsonException("problematic object: is not serializable");
}
writer.WriteBooleanValue(value.Deserializable);
}
}
}
}
24 changes: 24 additions & 0 deletions samples/DtsPortableSdkEntityTests/common/Test.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.DurableTask;

namespace DtsPortableSdkEntityTests;

internal abstract class Test
{
public virtual string Name => this.GetType().Name;

public abstract Task RunAsync(TestContext context);

public virtual TimeSpan Timeout => TimeSpan.FromSeconds(30);

public virtual void Register(DurableTaskRegistry registry, IServiceCollection services)
{
}
}
33 changes: 33 additions & 0 deletions samples/DtsPortableSdkEntityTests/common/TestContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.Entities;
using Microsoft.DurableTask.Entities;
using Microsoft.Extensions.Logging;

namespace DtsPortableSdkEntityTests;

internal class TestContext
{
public TestContext(DurableTaskClient client, ILogger logger, CancellationToken cancellationToken)
{
this.Client = client;
this.Logger = logger;
this.CancellationToken = cancellationToken;
}

public DurableTaskClient Client { get; }

public ILogger Logger { get; }

public CancellationToken CancellationToken { get; set; }

public bool BackendSupportsImplicitEntityDeletion { get; set; } = true; // false for Azure Storage, true for Netherite, MSSQL, and DTS
}
77 changes: 77 additions & 0 deletions samples/DtsPortableSdkEntityTests/common/TestContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.DurableTask.Client.Entities;
using Microsoft.DurableTask.Entities;
using Microsoft.Extensions.Logging;

namespace DtsPortableSdkEntityTests;

internal static class TestContextExtensions
{
public static async Task<T> WaitForEntityStateAsync<T>(
this TestContext context,
EntityInstanceId entityInstanceId,
TimeSpan? timeout = null,
Func<T, string?>? describeWhatWeAreWaitingFor = null)
{
if (timeout == null)
{
timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30);
}

Stopwatch sw = Stopwatch.StartNew();

EntityMetadata? response;

do
{
response = await context.Client.Entities.GetEntityAsync(entityInstanceId, includeState: true);

if (response != null)
{
if (describeWhatWeAreWaitingFor == null)
{
break;
}
else
{
var waitForResult = describeWhatWeAreWaitingFor(response.State.ReadAs<T>());

if (string.IsNullOrEmpty(waitForResult))
{
break;
}
else
{
context.Logger.LogInformation($"Waiting for {entityInstanceId} : {waitForResult}");
}
}
}
else
{
context.Logger.LogInformation($"Waiting for {entityInstanceId} to have state.");
}

await Task.Delay(TimeSpan.FromMilliseconds(100));
}
while (sw.Elapsed < timeout);

if (response != null)
{
string serializedState = response.State.Value;
context.Logger.LogInformation($"Found state: {serializedState}");
return response.State.ReadAs<T>();
}
else
{
throw new TimeoutException($"Durable entity '{entityInstanceId}' still doesn't have any state!");
}
}
}
Loading
Loading