Skip to content
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

Isolated Entity Tests #2612

Open
wants to merge 36 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c36d3d9
udpate readme.
sebastianburckhardt Sep 7, 2023
10111e0
update durability provider class for new core-entities support. (#2570)
sebastianburckhardt Sep 11, 2023
6ff3e7b
update DurableClient to take advantage of native entity queries (#2571)
sebastianburckhardt Sep 11, 2023
d1d6074
implement passthrough middleware for entities (#2572)
sebastianburckhardt Sep 11, 2023
eb961e0
implement entity queries for grpc listener (#2573)
sebastianburckhardt Sep 11, 2023
cc7b93a
Various fixes (#2585)
sebastianburckhardt Sep 19, 2023
db60e7f
simplify how entities are excluded from instance queries (#2586)
sebastianburckhardt Sep 19, 2023
2c5a7e5
add an entity example to the DotNetIsolated smoke test project. (#2584)
sebastianburckhardt Sep 21, 2023
ac6e0d2
Entities: Add worker side entity trigger and logic (#2576)
jviau Sep 21, 2023
6e9d615
Merge commit '2455636cca50cfb4d1543633d28657437bbb5173' into feature/…
sebastianburckhardt Sep 21, 2023
6251fca
another small fix that got lost somewhere. (#2596)
sebastianburckhardt Sep 25, 2023
06d0713
Update packages and version for entities preview (#2599)
jviau Sep 26, 2023
5429a27
Switch to Microsoft.DurableTask.Grpc (#2605)
jviau Sep 27, 2023
991c8f0
Fix grpc core (#2616)
jviau Oct 4, 2023
9f4cb5b
new test suite for isolated entities.
sebastianburckhardt Oct 5, 2023
0e26d15
pass entity parameters for task orchestration. (#2611)
sebastianburckhardt Oct 5, 2023
07ecbc8
Core entities/various fixes and updates (#2619)
sebastianburckhardt Oct 5, 2023
62d7049
Update to entities preview 2 (#2620)
jviau Oct 6, 2023
9e311a6
Add callback handler for entity dispatching (#2624)
jviau Oct 6, 2023
1edc10e
Merge branch 'feature/core-entities' into core-entities/isolated-tests
sebastianburckhardt Oct 6, 2023
a6b3622
propagate changes
sebastianburckhardt Oct 6, 2023
c4a89b0
Core entities/propagate changes (#2625)
sebastianburckhardt Oct 9, 2023
565d548
Rev dependencies to entities-preview.2 (#2627)
jviau Oct 9, 2023
cc0d0ed
Call EnsureLegalAccess from EntityFeature in dotnet-isolated (#2633)
jviau Oct 10, 2023
c545e42
create a better error message in situations where client entity funct…
sebastianburckhardt Oct 12, 2023
105948d
Merge branch 'feature/core-entities' into core-entities/isolated-tests
sebastianburckhardt Oct 12, 2023
9971383
address PR feedback
sebastianburckhardt Oct 17, 2023
219d3e8
Merge branch 'dev' into core-entities/isolated-tests
sebastianburckhardt Oct 20, 2023
82a1e8c
fix merge error
sebastianburckhardt Oct 20, 2023
d62c836
Merge branch 'dev' into core-entities/isolated-tests
sebastianburckhardt Dec 13, 2023
a4c370c
update SignalThenPoll test so it passes a non-null input, so that we …
sebastianburckhardt Dec 13, 2023
4171037
Merge branch 'dev' into core-entities/isolated-tests
sebastianburckhardt Feb 16, 2024
417f650
add test for faulty critical section.
sebastianburckhardt Feb 16, 2024
37ef463
add distinction on whether backend supports implicit deletion
sebastianburckhardt Feb 28, 2024
179f7e1
add non-entity tests for failure propagation by activities and suborc…
sebastianburckhardt Feb 29, 2024
8123d17
refine the entity error tests to check for nested failure details (in…
sebastianburckhardt Mar 1, 2024
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
7 changes: 7 additions & 0 deletions WebJobs.Extensions.DurableTask.sln
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PerfTests", "PerfTests", "{
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DFPerfScenariosV4", "test\DFPerfScenarios\DFPerfScenariosV4.csproj", "{FC8AD123-F949-4D21-B817-E5A4BBF7F69B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsolatedEntities", "test\IsolatedEntities\IsolatedEntities.csproj", "{8CBB856D-2D77-4052-9E50-2F635DE5C88F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -178,6 +180,10 @@ Global
{FC8AD123-F949-4D21-B817-E5A4BBF7F69B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC8AD123-F949-4D21-B817-E5A4BBF7F69B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC8AD123-F949-4D21-B817-E5A4BBF7F69B}.Release|Any CPU.Build.0 = Release|Any CPU
{8CBB856D-2D77-4052-9E50-2F635DE5C88F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CBB856D-2D77-4052-9E50-2F635DE5C88F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CBB856D-2D77-4052-9E50-2F635DE5C88F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CBB856D-2D77-4052-9E50-2F635DE5C88F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -211,6 +217,7 @@ Global
{65F904AA-0F6F-48CB-BE19-593B7D68152A} = {7387E723-E153-4B7A-B105-8C67BFBD48CF}
{7387E723-E153-4B7A-B105-8C67BFBD48CF} = {78BCF152-C22C-408F-9FB1-0F8C99B154B5}
{FC8AD123-F949-4D21-B817-E5A4BBF7F69B} = {7387E723-E153-4B7A-B105-8C67BFBD48CF}
{8CBB856D-2D77-4052-9E50-2F635DE5C88F} = {78BCF152-C22C-408F-9FB1-0F8C99B154B5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5E9AC327-DE18-41A5-A55D-E44CB4281943}
Expand Down
79 changes: 79 additions & 0 deletions test/IsolatedEntities/Common/HttpTriggers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using Azure.Core;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.DurableTask;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Entities;
using Microsoft.Extensions.Logging;

namespace IsolatedEntities;

/// <summary>
/// Provides an http trigger to run functional tests for entities.
/// </summary>
public static class HttpTriggers
{
[Function(nameof(RunAllTests))]
public static async Task<HttpResponseData> RunAllTests(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "tests/")] HttpRequestData request,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext)
{
var context = new TestContext(client, executionContext);
string result = await TestRunner.RunAsync(context, filter: null);
HttpResponseData response = request.CreateResponse(System.Net.HttpStatusCode.OK);
response.WriteString(result);
return response;
}

[Function(nameof(RunFilteredTests))]
public static async Task<HttpResponseData> RunFilteredTests(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "tests/{filter}")] HttpRequestData request,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext,
string filter)
{
var context = new TestContext(client, executionContext);
string result = await TestRunner.RunAsync(context, filter);
HttpResponseData response = request.CreateResponse(System.Net.HttpStatusCode.OK);
response.WriteString(result);
return response;
}

[Function(nameof(ListAllTests))]
public static async Task<HttpResponseData> ListAllTests(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "tests/")] HttpRequestData request,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext)
{
var context = new TestContext(client, executionContext);
string result = await TestRunner.RunAsync(context, filter: null, listOnly: true);
HttpResponseData response = request.CreateResponse(System.Net.HttpStatusCode.OK);
response.WriteString(result);
return response;
}

[Function(nameof(ListFilteredTests))]
public static async Task<HttpResponseData> ListFilteredTests(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "tests/{filter}")] HttpRequestData request,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext,
string filter)
{
var context = new TestContext(client, executionContext);
string result = await TestRunner.RunAsync(context, filter, listOnly: true);
HttpResponseData response = request.CreateResponse(System.Net.HttpStatusCode.OK);
response.WriteString(result);
return response;
}
}



69 changes: 69 additions & 0 deletions test/IsolatedEntities/Common/ProblematicObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

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 IsolatedEntities
{
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);
}

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);
}
}
}
}
19 changes: 19 additions & 0 deletions test/IsolatedEntities/Common/Test.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

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

namespace IsolatedEntities;

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

public abstract Task RunAsync(TestContext context);

public virtual TimeSpan Timeout => TimeSpan.FromSeconds(30);
}
36 changes: 36 additions & 0 deletions test/IsolatedEntities/Common/TestContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

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

namespace IsolatedEntities;

internal class TestContext
{
public TestContext(DurableTaskClient client, FunctionContext executionContext)
{
this.ExecutionContext = executionContext;
this.Client = client;
this.Logger = executionContext.GetLogger(nameof(IsolatedEntities));
}

public FunctionContext ExecutionContext { get; }

public DurableTaskClient Client { get; }

public ILogger Logger { get; }

public CancellationToken CancellationToken { get; set; }

public bool BackendSupportsImplicitEntityDeletion { get; set; } = false; // false for Azure Storage, true for Netherite and MSSQL
}
77 changes: 77 additions & 0 deletions test/IsolatedEntities/Common/TestContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

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 IsolatedEntities;

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!");
}
}
}
57 changes: 57 additions & 0 deletions test/IsolatedEntities/Common/TestRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace IsolatedEntities;

internal static class TestRunner
{
public static async Task<string> RunAsync(TestContext context, string? filter = null, bool listOnly = false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit surprised that we're writing our own test runner. I'm not strictly against it (plus this is relatively simple) but why why aren't we scaffolding this with our usual testing framework?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has to run inside a function app. I had no appetite for spending a lot of time figuring out how to get a unit testing framework running inside a function app. Also, my past experience with those frameworks is not great. Takes a lot to get them to do what you want, even if what you want is rather simple. As demonstrated with this little bit of code here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I am not sure we should be adding our own test runner. What I recommend is using xunit test fixtures. You can encapsulate starting the function app via this fixture and then author tests which pull in and start the fixture (this starting the function app), and then dispatch HTTP to the function app to run a given scenario.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand what you are suggesting, we would additionally create a separate xunit test for each test and then have that test invoke the app's unit test via http call?

Would that be a separate project (given that the function app is a special kind of project)? Would there be any way to avoid having to redefine all tests in both projects?

{
var output = new StringBuilder();

foreach (var test in All.GetAllTests())
{
if (filter == null || test.Name.ToLowerInvariant().Equals(filter.ToLowerInvariant()))
{
if (listOnly)
{
output.AppendLine(test.Name);
}
else
{
context.Logger.LogWarning("------------ starting {testName}", test.Name);

// if debugging, time out after 60m
// otherwise, time out either when the http request times out or when the individual test time limit is exceeded
using CancellationTokenSource cancellationTokenSource
= Debugger.IsAttached ? new() : CancellationTokenSource.CreateLinkedTokenSource(context.ExecutionContext.CancellationToken);
cancellationTokenSource.CancelAfter(Debugger.IsAttached ? TimeSpan.FromMinutes(60) : test.Timeout);
context.CancellationToken = cancellationTokenSource.Token;

try
{
await test.RunAsync(context);
output.AppendLine($"PASSED {test.Name}");
}
catch (Exception ex)
{
context.Logger.LogError(ex, "test {testName} failed", test.Name);
output.AppendLine($"FAILED {test.Name} {ex.ToString()}");
break;
}
}
}
}

return output.ToString();
}
}
Loading
Loading