Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanwoctopusdeploy committed Dec 5, 2023
1 parent a19e956 commit c8fdca0
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,15 @@ public ScriptOrchestratorFactory(

public async Task<IScriptOrchestrator> CreateOrchestrator(CancellationToken cancellationToken)
{
var scriptServiceToUse = await DetermineScriptServiceVersionToUse(cancellationToken);
ScriptServiceVersion scriptServiceToUse;
try
{
scriptServiceToUse = await DetermineScriptServiceVersionToUse(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw new OperationCanceledException("Script execution was cancelled");
}

return scriptServiceToUse switch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,10 @@ public async Task DuringGetCapabilities_ScriptExecutionCanBeCancelled(TentacleCo
var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall);

// ASSERT
// The ExecuteScript operation threw an OperationCancelledException
actualException.Should().BeTaskOrOperationCancelledException();
var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase, clientAndTentacle)
.ForScriptService(ScriptServiceOperation.GetCapabilities).Build();

actualException.ShouldMatchExceptionContract(expectedException);

// If the rpc call could be cancelled then the correct error was recorded
var latestException = capabilitiesMethodUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).LastException;
Expand Down Expand Up @@ -185,8 +187,10 @@ public async Task DuringStartScript_ScriptExecutionCanBeCancelled(TentacleConfig
var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall);

// ASSERT
// The ExecuteScript operation threw an OperationCancelledException
actualException.Should().BeTaskOrOperationCancelledException();
var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase, clientAndTentacle)
.ForScriptService(ScriptServiceOperation.StartScript).Build();

actualException.ShouldMatchExceptionContract(expectedException);

// If the rpc call could be cancelled then the correct error was recorded
var latestException = recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).LastException;
Expand Down Expand Up @@ -303,8 +307,10 @@ public async Task DuringGetStatus_ScriptExecutionCanBeCancelled(TentacleConfigur
var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall);

// ASSERT
// The ExecuteScript operation threw an OperationCancelledException
actualException.Should().BeTaskOrOperationCancelledException();
var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase, clientAndTentacle)
.ForScriptService(ScriptServiceOperation.GetStatus).Build();

actualException.ShouldMatchExceptionContract(expectedException);

// If the rpc call could be cancelled then the correct error was recorded
var latestException = recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException;
Expand Down Expand Up @@ -384,9 +390,14 @@ public async Task DuringCompleteScript_ScriptExecutionCanBeCancelled(TentacleCon
.Build();

// ACT
var (responseAndLogs, _, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, new SemaphoreSlim(Int32.MaxValue, Int32.MaxValue));
var (responseAndLogs, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, new SemaphoreSlim(int.MaxValue, Int32.MaxValue));

// ASSERT
var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase, clientAndTentacle)
.ForScriptService(ScriptServiceOperation.CompleteScript).Build();

actualException.ShouldMatchExceptionContract(expectedException);

// Halibut Errors were recorded on CompleteScript
recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).LastException?.Should().Match<Exception>(x => x is HalibutClientException || x is OperationCanceledException || x is TaskCanceledException); // Complete Script was cancelled quickly
cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ public async Task DuringGetCapabilities_ScriptExecutionCanBeCancelled(TentacleCo
var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall);

// ASSERT
// The ExecuteScript operation threw an OperationCancelledException
actualException.Should().BeTaskOrOperationCancelledException();
var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase, clientAndTentacle)
.ForScriptService(ScriptServiceOperation.GetCapabilities).Build();

actualException.ShouldMatchExceptionContract(expectedException);

// If the rpc call could be cancelled then the correct error was recorded
var latestException = capabilitiesMethodUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).LastException;
Expand Down Expand Up @@ -235,8 +237,10 @@ public async Task DuringStartScript_ScriptExecutionCanBeCancelled(TentacleConfig
var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall);

// ASSERT
// The ExecuteScript operation threw an OperationCancelledException
actualException.Should().BeTaskOrOperationCancelledException();
var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase, clientAndTentacle)
.ForScriptService(ScriptServiceOperation.StartScript).Build();

actualException.ShouldMatchExceptionContract(expectedException);

// If the rpc call could be cancelled then the correct error was recorded
var latestException = recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).LastException;
Expand Down Expand Up @@ -390,8 +394,10 @@ public async Task DuringGetStatus_ScriptExecutionCanBeCancelled(TentacleConfigur
var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall);

// ASSERT
// The ExecuteScript operation threw an OperationCancelledException
actualException.Should().BeTaskOrOperationCancelledException();
var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase, clientAndTentacle)
.ForScriptService(ScriptServiceOperation.GetStatus).Build();

actualException.ShouldMatchExceptionContract(expectedException);

// If the rpc call could be cancelled then the correct error was recorded
var latestException = recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException;
Expand Down Expand Up @@ -483,9 +489,14 @@ public async Task DuringCompleteScript_ScriptExecutionCanBeCancelled(TentacleCon
.Build();

// ACT
var (responseAndLogs, _, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, new SemaphoreSlim(int.MaxValue, int.MaxValue));
var (responseAndLogs, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, new SemaphoreSlim(int.MaxValue, int.MaxValue));

// ASSERT
var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase, clientAndTentacle)
.ForScriptService(ScriptServiceOperation.CompleteScript).Build();

actualException.ShouldMatchExceptionContract(expectedException);

// Halibut Errors were recorded on CompleteScript
recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).LastException?.Should().Match<Exception>(x => x is HalibutClientException || x is OperationCanceledException || x is TaskCanceledException);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,51 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Specialized;
using Halibut;
using Octopus.Tentacle.Tests.Integration.Support;

namespace Octopus.Tentacle.Tests.Integration
namespace Octopus.Tentacle.Tests.Integration.Support
{
public static class ExceptionContractAssertionBuilderExtensionMethods
{
public static async Task<ExceptionAssertions<Exception>> ThrowExceptionContractAsync(
this NonGenericAsyncFunctionAssertions should,
ExceptionContract expected,
this NonGenericAsyncFunctionAssertions should,
ExceptionContract expected,
string because = "",
params object[] becauseArgs)
{
var exceptionAssertions = await should.ThrowAsync<Exception>();
var exception = exceptionAssertions.And;

exception.Should().BeOfType(expected.ExceptionType, because, becauseArgs);
exception.Message.Should().ContainAny(expected.ExceptionMessageShouldContainAny, because, becauseArgs);
exception.ShouldMatchExceptionContract(expected, because, becauseArgs);

return exceptionAssertions;
}

public static async Task<ExceptionAssertions<Exception>> ThrowExceptionContractAsync<T>(
this GenericAsyncFunctionAssertions<T> should,
ExceptionContract expected,
this GenericAsyncFunctionAssertions<T> should,
ExceptionContract expected,
string because = "",
params object[] becauseArgs)
{
var exceptionAssertions = await should.ThrowAsync<Exception>();
var exception = exceptionAssertions.And;

exception.Should().BeOfType(expected.ExceptionType, because, becauseArgs);
exception.Message.Should().ContainAny(expected.ExceptionMessageShouldContainAny, because, becauseArgs);
exception.ShouldMatchExceptionContract(expected, because, becauseArgs);

return exceptionAssertions;
}

public static void ShouldMatchExceptionContract(
this Exception exception,
ExceptionContract expected,
string because = "",
params object[] becauseArgs)
{
exception.Should().Match(x => expected.ExceptionTypes.Contains(x.GetType()), because, becauseArgs);
exception.Message.Should().ContainAny(expected.ExceptionMessageShouldContainAny, because, becauseArgs);
}
}

public class ExceptionContractAssertionBuilder
Expand Down Expand Up @@ -96,10 +104,10 @@ public ExceptionContractAssertionBuilder ForScriptService(ScriptServiceOperation

return this;
}

public ExceptionContractAssertionBuilder ForFileTransferService(FileTransferServiceOperation fileTransferSercviceOperation)
{
this.fileTransferServiceOperation = fileTransferSercviceOperation;
fileTransferServiceOperation = fileTransferSercviceOperation;

return this;
}
Expand All @@ -120,7 +128,7 @@ public ExceptionContract Build()
{
throw new InvalidOperationException("Script Service Version not specified in the TentacleConfigurationTestCase");
}

if (fileTransferServiceOperation != null)
{
if (failureScenario == FailureScenario.ConnectionFaulted)
Expand All @@ -133,6 +141,7 @@ public ExceptionContract Build()
$"An error occurred when sending a request to '{clientAndTentacle.ServiceEndPoint}/', after the request began: Attempted to read past the end of the stream.",
$"An error occurred when sending a request to '{clientAndTentacle.ServiceEndPoint}/', after the request began: Unable to write data to the transport connection: An established connection was aborted by the software in your host machine",
$"An error occurred when sending a request to '{clientAndTentacle.ServiceEndPoint}/', after the request began: Unable to read data from the transport connection: An established connection was aborted by the software in your host machine",
$"An error occurred when sending a request to '{clientAndTentacle.ServiceEndPoint}/', after the request began: Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host",
$"An error occurred when sending a request to '{clientAndTentacle.ServiceEndPoint}/', before the request could begin: Connection refused"
});
case TentacleType.Polling:
Expand All @@ -142,16 +151,29 @@ public ExceptionContract Build()
"Unable to write data to the transport connection: An established connection was aborted by the software in your host machine",
"Unable to read data from the transport connection: An established connection was aborted by the software in your host machine",
"Connection refused",
"Unable to write data to the transport connection: Broken pipe"
"Unable to write data to the transport connection: Broken pipe",
"Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host"
});
default:
throw new ArgumentOutOfRangeException();
}
}
}
else
else if (scriptServiceOperation != null)
{

if (failureScenario == FailureScenario.ScriptExecutionCancelled)
{
return new ExceptionContract(
new[]{
typeof(OperationCanceledException),
typeof(TaskCanceledException)
},
new[]
{
"Script execution was cancelled",
"A task was canceled." // Cancellation during StartScript while connecting throws the wrong error
});
}
}

throw new NotImplementedException();
Expand All @@ -160,12 +182,18 @@ public ExceptionContract Build()

public class ExceptionContract
{
public Type ExceptionType { get; }
public Type[] ExceptionTypes { get; }
public string[] ExceptionMessageShouldContainAny { get; }

public ExceptionContract(Type exceptionType, string[] exceptionMessageShouldContainAny)
public ExceptionContract(Type exceptionTypes, string[] exceptionMessageShouldContainAny)
{
ExceptionTypes = new[] { exceptionTypes };
ExceptionMessageShouldContainAny = exceptionMessageShouldContainAny;
}

public ExceptionContract(Type[] exceptionTypes, string[] exceptionMessageShouldContainAny)
{
ExceptionType = exceptionType;
ExceptionTypes = exceptionTypes;
ExceptionMessageShouldContainAny = exceptionMessageShouldContainAny;
}
}
Expand All @@ -186,7 +214,8 @@ public enum FileTransferServiceOperation
}

public enum FailureScenario
{
ConnectionFaulted
{
ConnectionFaulted,
ScriptExecutionCancelled
}
}

0 comments on commit c8fdca0

Please sign in to comment.