Skip to content

Commit

Permalink
Add cancellation e2e tests
Browse files Browse the repository at this point in the history
  • Loading branch information
liliankasem committed Jan 13, 2025
1 parent d242ed6 commit a975433
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 6 deletions.
4 changes: 3 additions & 1 deletion test/E2ETests/E2EApps/E2EApp/E2EApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\extensions\Worker.Extensions.Storage\src\Worker.Extensions.Storage.csproj" />
<ProjectReference Include="..\..\..\..\extensions\Worker.Extensions.Abstractions\src\Worker.Extensions.Abstractions.csproj" />
<ProjectReference Include="..\..\..\..\extensions\Worker.Extensions.CosmosDB\src\Worker.Extensions.CosmosDB.csproj" />
<ProjectReference Include="..\..\..\..\extensions\Worker.Extensions.EventHubs\src\Worker.Extensions.EventHubs.csproj" />
<ProjectReference Include="..\..\..\..\extensions\Worker.Extensions.Http\src\Worker.Extensions.Http.csproj" />
<ProjectReference Include="..\..\..\..\extensions/Worker.Extensions.Http.AspNetCore\src\Worker.Extensions.Http.AspNetCore.csproj" />
<ProjectReference Include="..\..\..\..\extensions\Worker.Extensions.Storage\src\Worker.Extensions.Storage.csproj" />
<ProjectReference Include="..\..\..\..\extensions\Worker.Extensions.Timer\src\Worker.Extensions.Timer.csproj" />
<ProjectReference Include="..\..\..\..\extensions\Worker.Extensions.Tables\src\Worker.Extensions.Tables.csproj" />
</ItemGroup>
Expand All @@ -41,6 +42,7 @@
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<!-- Use version 2.2.0 for .NET Framework 4.8 -->
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" Condition="'$(TargetFramework)' == 'net48'" />
<!-- Use version 8.0.1 for .NET Core or other target frameworks -->
Expand Down
78 changes: 78 additions & 0 deletions test/E2ETests/E2EApps/E2EApp/Http/CancellationHttpFunctions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;

namespace Microsoft.Azure.Functions.Worker.E2EApp
{
public class CancellationHttpFunctions(ILogger<CancellationHttpFunctions> logger)
{
private readonly ILogger<CancellationHttpFunctions> _logger = logger;

[Function(nameof(HttpWithCancellationTokenNotUsed))]
public async Task<IActionResult> HttpWithCancellationTokenNotUsed(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
{
_logger.LogInformation("HttpWithCancellationTokenNotUsed processed a request.");

await SimulateWork(CancellationToken.None);

return new OkObjectResult("Processing completed successfully.");
}

[Function(nameof(HttpWithCancellationTokenIgnored))]
public async Task<IActionResult> HttpWithCancellationTokenIgnored(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req,
CancellationToken cancellationToken)
{
_logger.LogInformation("HttpWithCancellationTokenIgnored processed a request.");

await SimulateWork(cancellationToken);

return new OkObjectResult("Processing completed successfully.");
}

[Function(nameof(HttpWithCancellationTokenHandled))]
public async Task<IActionResult> HttpWithCancellationTokenHandled(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
CancellationToken cancellationToken)
{
_logger.LogInformation("HttpWithCancellationTokenHandled processed a request.");

try
{
await SimulateWork(cancellationToken);

return new OkObjectResult("Processing completed successfully.");
}
catch (OperationCanceledException)
{
_logger.LogWarning("Request was cancelled.");

// Take precautions like noting how far along you are with processing the batch
await Task.Delay(1000);

return new ObjectResult(new { statusCode = StatusCodes.Status499ClientClosedRequest, message = "Request was cancelled." });
}
}

private async Task SimulateWork(CancellationToken cancellationToken)
{
_logger.LogInformation("Starting work...");

for (int i = 0; i < 5; i++)
{
// Simulate work
await Task.Delay(1000, cancellationToken);
_logger.LogWarning($"Work iteration {i + 1} completed.");
}

_logger.LogInformation("Work completed.");
}
}
}
5 changes: 4 additions & 1 deletion test/E2ETests/E2ETests/Fixtures/FunctionAppFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class FunctionAppFixture : IAsyncLifetime

public FunctionAppFixture(IMessageSink messageSink)
{
// initialize logging
// initialize logging
ILoggerFactory loggerFactory = new LoggerFactory();
TestLogs = new TestLoggerProvider(messageSink);
loggerFactory.AddProvider(TestLogs);
Expand Down Expand Up @@ -63,6 +63,9 @@ public async Task InitializeAsync()
_funcProcess.StartInfo.ArgumentList.Add("PocoWithoutBindingSource");
_funcProcess.StartInfo.ArgumentList.Add("HelloPascal");
_funcProcess.StartInfo.ArgumentList.Add("HelloAllCaps");
_funcProcess.StartInfo.ArgumentList.Add("HttpWithCancellationTokenNotUsed");
_funcProcess.StartInfo.ArgumentList.Add("HttpWithCancellationTokenIgnored");
_funcProcess.StartInfo.ArgumentList.Add("HttpWithCancellationTokenHandled");
}

await CosmosDBHelpers.TryCreateDocumentCollectionsAsync(_logger);
Expand Down
56 changes: 56 additions & 0 deletions test/E2ETests/E2ETests/Helpers/CancellationEndToEndTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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.Threading;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Azure.Functions.Tests.E2ETests
{
[Collection(Constants.FunctionAppCollectionName)]
public class CancellationEndToEndTests
{
private readonly FunctionAppFixture _fixture;

public CancellationEndToEndTests(FunctionAppFixture fixture, ITestOutputHelper testOutputHelper)
{
_fixture = fixture;
_fixture.TestLogs.UseTestLogger(testOutputHelper);
}

[Theory]
[InlineData("HttpWithCancellationTokenNotUsed", "Work completed.", "Succeeded")]
[InlineData("HttpWithCancellationTokenIgnored", "TaskCanceledException: A task was canceled", "Failed")]
[InlineData("HttpWithCancellationTokenHandled", "Request was cancelled.", "Succeeded")]
public async Task Functions_WithCancellationToken_BehaveAsExpected(string functionName, string expectedMessage, string invocationResult)
{
using var cts = new CancellationTokenSource();

try
{
var task = HttpHelpers.InvokeHttpTrigger(functionName, cancellationToken: cts.Token);

await Task.Delay(3000);
cts.Cancel();

var response = await task;
}
catch (Exception)
{
IEnumerable<string> logs = null;
await TestUtility.RetryAsync(() =>
{
logs = _fixture.TestLogs.CoreToolsLogs.Where(p => p.Contains($"Executed 'Functions.{functionName}' ({invocationResult}"));

return Task.FromResult(logs.Count() >= 1);
});

Assert.Contains(_fixture.TestLogs.CoreToolsLogs, log => log.Contains(expectedMessage, StringComparison.OrdinalIgnoreCase));
}
}
}
}
9 changes: 5 additions & 4 deletions test/E2ETests/E2ETests/Helpers/HttpHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Azure.Functions.Tests.E2ETests
{
class HttpHelpers
{
public static async Task<HttpResponseMessage> InvokeHttpTrigger(string functionName, string queryString = "")
public static async Task<HttpResponseMessage> InvokeHttpTrigger(string functionName, string queryString = "", CancellationToken cancellationToken = default)
{
// Basic http request
HttpRequestMessage request = GetTestRequest(functionName, queryString);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
return await GetResponseMessage(request);
return await GetResponseMessage(request, cancellationToken);
}

public static async Task<HttpResponseMessage> InvokeHttpTriggerWithBody(string functionName, string body, string mediaType)
Expand Down Expand Up @@ -64,12 +65,12 @@ private static HttpRequestMessage GetTestRequest(string functionName, string que
};
}

private static async Task<HttpResponseMessage> GetResponseMessage(HttpRequestMessage request)
private static async Task<HttpResponseMessage> GetResponseMessage(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
HttpResponseMessage response = null;
using (var httpClient = new HttpClient())
{
response = await httpClient.SendAsync(request);
response = await httpClient.SendAsync(request, cancellationToken);
}

return response;
Expand Down

0 comments on commit a975433

Please sign in to comment.