diff --git a/source/Octopus.Tentacle.Tests.Integration/CapabilitiesServiceV2Test.cs b/source/Octopus.Tentacle.Tests.Integration/CapabilitiesServiceV2Test.cs index 6f9b7300a..bbe894108 100644 --- a/source/Octopus.Tentacle.Tests.Integration/CapabilitiesServiceV2Test.cs +++ b/source/Octopus.Tentacle.Tests.Integration/CapabilitiesServiceV2Test.cs @@ -1,126 +1,126 @@ -#nullable enable -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut; -using NUnit.Framework; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.Capabilities; -using Octopus.Tentacle.Contracts.KubernetesScriptServiceV1; -using Octopus.Tentacle.Contracts.ScriptServiceV2; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util.Builders; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class CapabilitiesServiceV2Test : IntegrationTest - { - [Test] - [TentacleConfigurations(testCapabilitiesServiceVersions: true)] - public async Task CapabilitiesFromAnOlderTentacleWhichHasNoCapabilitiesService_WorksWithTheBackwardsCompatabilityDecorator(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - Version? version = tentacleConfigurationTestCase.Version; - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateLegacyBuilder().Build(CancellationToken); - - var capabilities = (await clientAndTentacle.TentacleClient.CapabilitiesServiceV2.GetCapabilitiesAsync(new(CancellationToken))).SupportedCapabilities; - - capabilities.Should().Contain(nameof(IScriptService)); - capabilities.Should().Contain(nameof(IFileTransferService)); - - //all versions have ScriptServiceV1 & IFileTransferService - var expectedCapabilitiesCount = 2; - if (version.HasScriptServiceV2()) - { - capabilities.Should().Contain(nameof(IScriptServiceV2)); - expectedCapabilitiesCount++; - } - - capabilities.Count.Should().Be(expectedCapabilitiesCount); - } - - [Test] - [TentacleConfigurations] - public async Task CapabilitiesServiceDoesNotReturnKubernetesScriptServiceForNonKubernetesTentacle(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var version = tentacleConfigurationTestCase.Version; - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateLegacyBuilder().Build(CancellationToken); - - var capabilities = (await clientAndTentacle.TentacleClient.CapabilitiesServiceV2.GetCapabilitiesAsync(new(CancellationToken))).SupportedCapabilities; - - capabilities.Should().Contain(nameof(IScriptService)); - capabilities.Should().Contain(nameof(IFileTransferService)); - - //all versions have ScriptServiceV1 & IFileTransferService - var expectedCapabilitiesCount = 2; - if (version.HasScriptServiceV2()) - { - capabilities.Should().Contain(nameof(IScriptServiceV2)); - expectedCapabilitiesCount++; - } - - capabilities.Should().NotContain(nameof(IKubernetesScriptServiceV1)); - - capabilities.Count.Should().Be(expectedCapabilitiesCount); - } - - [Test] - [TentacleConfigurations(testCapabilitiesServiceVersions: true)] - public async Task CapabilitiesResponseShouldBeCached(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var capabilitiesResponses = new List(); - var resumePortForwarder = false; - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPortForwarder(out var portForwarder) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .DecorateCapabilitiesServiceV2With(d => d - .DecorateGetCapabilitiesWith( - async (inner, options) => - { - var response = await inner.GetCapabilitiesAsync(options); - - capabilitiesResponses.Add(response); - - if (resumePortForwarder) - { - // (2) Once a get capabilities call has been made which uses the cached response then resume normal RPC calls - // to allow script execution to continue - portForwarder.Value.ReturnToNormalMode(); - } - - return response; - })) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("Running...")) - .Build(); - - await clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - - // (1) Kill new and existing connections to ensure no RPC calls can be made - clientAndTentacle.PortForwarder!.EnterKillNewAndExistingConnectionsMode(); - resumePortForwarder = true; - - try - { - await clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - } - catch (HalibutClientException) when (tentacleConfigurationTestCase.TentacleType == TentacleType.Polling && tentacleConfigurationTestCase.Version != null) - { - // For script execution on a tentacle without ScriptServiceV2 and retries a polling request can be de-queued into a broken TCP Connection - // By the time this happens we will have already called gt capabilities and got the cached response so we can safely ignore. - } - - capabilitiesResponses.Should().HaveCount(2); - capabilitiesResponses[0].Should().BeEquivalentTo(capabilitiesResponses[1]); - } - } -} +// #nullable enable +// using System; +// using System.Collections.Generic; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut; +// using NUnit.Framework; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Contracts.Capabilities; +// using Octopus.Tentacle.Contracts.KubernetesScriptServiceV1; +// using Octopus.Tentacle.Contracts.ScriptServiceV2; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class CapabilitiesServiceV2Test : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(testCapabilitiesServiceVersions: true)] +// public async Task CapabilitiesFromAnOlderTentacleWhichHasNoCapabilitiesService_WorksWithTheBackwardsCompatabilityDecorator(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// Version? version = tentacleConfigurationTestCase.Version; +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateLegacyBuilder().Build(CancellationToken); +// +// var capabilities = (await clientAndTentacle.TentacleClient.CapabilitiesServiceV2.GetCapabilitiesAsync(new(CancellationToken))).SupportedCapabilities; +// +// capabilities.Should().Contain(nameof(IScriptService)); +// capabilities.Should().Contain(nameof(IFileTransferService)); +// +// //all versions have ScriptServiceV1 & IFileTransferService +// var expectedCapabilitiesCount = 2; +// if (version.HasScriptServiceV2()) +// { +// capabilities.Should().Contain(nameof(IScriptServiceV2)); +// expectedCapabilitiesCount++; +// } +// +// capabilities.Count.Should().Be(expectedCapabilitiesCount); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task CapabilitiesServiceDoesNotReturnKubernetesScriptServiceForNonKubernetesTentacle(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var version = tentacleConfigurationTestCase.Version; +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateLegacyBuilder().Build(CancellationToken); +// +// var capabilities = (await clientAndTentacle.TentacleClient.CapabilitiesServiceV2.GetCapabilitiesAsync(new(CancellationToken))).SupportedCapabilities; +// +// capabilities.Should().Contain(nameof(IScriptService)); +// capabilities.Should().Contain(nameof(IFileTransferService)); +// +// //all versions have ScriptServiceV1 & IFileTransferService +// var expectedCapabilitiesCount = 2; +// if (version.HasScriptServiceV2()) +// { +// capabilities.Should().Contain(nameof(IScriptServiceV2)); +// expectedCapabilitiesCount++; +// } +// +// capabilities.Should().NotContain(nameof(IKubernetesScriptServiceV1)); +// +// capabilities.Count.Should().Be(expectedCapabilitiesCount); +// } +// +// [Test] +// [TentacleConfigurations(testCapabilitiesServiceVersions: true)] +// public async Task CapabilitiesResponseShouldBeCached(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var capabilitiesResponses = new List(); +// var resumePortForwarder = false; +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPortForwarder(out var portForwarder) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .DecorateCapabilitiesServiceV2With(d => d +// .DecorateGetCapabilitiesWith( +// async (inner, options) => +// { +// var response = await inner.GetCapabilitiesAsync(options); +// +// capabilitiesResponses.Add(response); +// +// if (resumePortForwarder) +// { +// // (2) Once a get capabilities call has been made which uses the cached response then resume normal RPC calls +// // to allow script execution to continue +// portForwarder.Value.ReturnToNormalMode(); +// } +// +// return response; +// })) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("Running...")) +// .Build(); +// +// await clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// +// // (1) Kill new and existing connections to ensure no RPC calls can be made +// clientAndTentacle.PortForwarder!.EnterKillNewAndExistingConnectionsMode(); +// resumePortForwarder = true; +// +// try +// { +// await clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// } +// catch (HalibutClientException) when (tentacleConfigurationTestCase.TentacleType == TentacleType.Polling && tentacleConfigurationTestCase.Version != null) +// { +// // For script execution on a tentacle without ScriptServiceV2 and retries a polling request can be de-queued into a broken TCP Connection +// // By the time this happens we will have already called gt capabilities and got the cached response so we can safely ignore. +// } +// +// capabilitiesResponses.Should().HaveCount(2); +// capabilitiesResponses[0].Should().BeEquivalentTo(capabilitiesResponses[1]); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientFileTransferRetriesTimeout.cs b/source/Octopus.Tentacle.Tests.Integration/ClientFileTransferRetriesTimeout.cs index d2e5784d1..1ea45517e 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientFileTransferRetriesTimeout.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientFileTransferRetriesTimeout.cs @@ -1,262 +1,262 @@ -using System; -using System.Collections; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils.Diagnostics; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; -using Octopus.TestPortForwarder; - -namespace Octopus.Tentacle.Tests.Integration -{ - /// - /// These tests make sure that we can cancel or walk away (if code does not cooperate with cancellation tokens) - /// from RPC calls when they are being retried and the rpc timeout period elapses. - /// - [IntegrationTestTimeout] - public class ClientFileTransferRetriesTimeout : IntegrationTest - { - readonly TimeSpan retryIfRemainingDurationAtLeastBuffer = TimeSpan.FromSeconds(1); - readonly TimeSpan retryBackoffBuffer = TimeSpan.FromSeconds(2); - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None, additionalParameterTypes: new object[] { typeof(StopPortForwarderAfterFirstCallValues) })] - public async Task WhenRpcRetriesTimeOut_DuringUploadFile_TheRpcCallIsCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, bool stopPortForwarderAfterFirstCall) - { - PortForwarder portForwarder = null!; - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - // Set a short retry duration so we cancel fairly quickly - .WithRetryDuration(TimeSpan.FromSeconds(15)) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var methodUsages) - .DecorateFileTransferServiceWith(d => d - .BeforeUploadFile( - async () => - { - if (methodUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).LastException is null) - { - // Ensure there is an active connection so it can be killed correctly - await tcpConnectionUtilities.RestartTcpConnection(); - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - else - { - if (stopPortForwarderAfterFirstCall) - { - // Kill the port forwarder so the next requests are in the connecting state when retries timeout - Logger.Information("Killing PortForwarder"); - portForwarder!.EnterKillNewAndExistingConnectionsMode(); - } - else - { - // Ensure there is an active connection so it can be killed correctly - await tcpConnectionUtilities.RestartTcpConnection(); - // Pause the port forwarder so the next requests are in-flight when retries timeout - responseMessageTcpKiller.PauseConnectionOnNextResponse(); - } - } - })) - .Build()) - .Build(CancellationToken); - - portForwarder = clientAndTentacle.PortForwarder; - - var inMemoryLog = new InMemoryLog(); - - var remotePath = Path.Combine(clientAndTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); - var dataStream = DataStream.FromString("The Stream"); - - // Start the script which will wait for a file to exist - var duration = Stopwatch.StartNew(); - var executeScriptTask = clientAndTentacle.TentacleClient.UploadFile(remotePath, dataStream, CancellationToken, inMemoryLog); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - duration.Stop(); - - methodUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).Started.Should().BeGreaterOrEqualTo(2); - methodUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).Started.Should().Be(0); - - // Ensure we actually waited and retried until the timeout policy kicked in - duration.Elapsed.Should().BeGreaterOrEqualTo(clientAndTentacle.RpcRetrySettings.RetryDuration - retryIfRemainingDurationAtLeastBuffer - retryBackoffBuffer); - - inMemoryLog.ShouldHaveLoggedRetryAttemptsAndRetryFailure(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task WhenUploadFileFails_AndTakesLongerThanTheRetryDuration_TheCallIsNotRetried_AndTimesOut(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var retryDuration = TimeSpan.FromSeconds(15); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - // Set a short retry duration so we cancel fairly quickly - .WithRetryDuration(retryDuration) - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var methodUsages) - .DecorateFileTransferServiceWith(d => d - .BeforeUploadFile( - async () => - { - await tcpConnectionUtilities.RestartTcpConnection(); - - // Sleep to make the initial RPC call take longer than the allowed retry duration - await Task.Delay(retryDuration + TimeSpan.FromSeconds(1)); - - // Kill the first UploadFile call to force the rpc call into retries - responseMessageTcpKiller.KillConnectionOnNextResponse(); - })) - .Build()) - .Build(CancellationToken); - - var inMemoryLog = new InMemoryLog(); - var remotePath = Path.Combine(clientAndTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); - var dataStream = DataStream.FromString("The Stream"); - var executeScriptTask = clientAndTentacle.TentacleClient.UploadFile(remotePath, dataStream, CancellationToken, inMemoryLog); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - methodUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).Started.Should().Be(1); - methodUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).Started.Should().Be(0); - - inMemoryLog.ShouldHaveLoggedRetryFailureAndNoRetryAttempts(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None, additionalParameterTypes: new object[] { typeof(StopPortForwarderAfterFirstCallValues) })] - public async Task WhenRpcRetriesTimeOut_DuringDownloadFile_TheRpcCallIsCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, bool stopPortForwarderAfterFirstCall) - { - PortForwarder portForwarder = null!; - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - // Set a short retry duration so we cancel fairly quickly - .WithRetryDuration(TimeSpan.FromSeconds(15)) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .DecorateFileTransferServiceWith(d => d - .BeforeDownloadFile( - async () => - { - if (recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).LastException is null) - { - // Ensure there is an active connection so it can be killed correctly - await tcpConnectionUtilities.RestartTcpConnection(); - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - else - { - - if (stopPortForwarderAfterFirstCall) - { - // Kill the port forwarder so the next requests are in the connecting state when retries timeout - Logger.Information("Killing PortForwarder"); - portForwarder!.Dispose(); - } - else - { - // Ensure there is an active connection so it can be killed correctly - await tcpConnectionUtilities.RestartTcpConnection(); - // Pause the port forwarder so the next requests are in-flight when retries timeout - responseMessageTcpKiller.PauseConnectionOnNextResponse(); - } - } - })) - .Build()) - .Build(CancellationToken); - - portForwarder = clientAndTentacle.PortForwarder; - - using var tempFile = new RandomTemporaryFileBuilder().Build(); - - var inMemoryLog = new InMemoryLog(); - - // Start the script which will wait for a file to exist - var duration = Stopwatch.StartNew(); - var executeScriptTask = clientAndTentacle.TentacleClient.DownloadFile(tempFile.File.FullName, CancellationToken, inMemoryLog); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - duration.Stop(); - - recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).Started.Should().BeGreaterOrEqualTo(2); - recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).Started.Should().Be(0); - - // Ensure we actually waited and retried until the timeout policy kicked in - duration.Elapsed.Should().BeGreaterOrEqualTo(clientAndTentacle.RpcRetrySettings.RetryDuration - retryIfRemainingDurationAtLeastBuffer - retryBackoffBuffer); - - inMemoryLog.ShouldHaveLoggedRetryAttemptsAndRetryFailure(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task WhenDownloadFileFails_AndTakesLongerThanTheRetryDuration_TheCallIsNotRetried_AndTimesOut(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var retryDuration = TimeSpan.FromSeconds(15); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - // Set a short retry duration so we cancel fairly quickly - .WithRetryDuration(retryDuration) - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .DecorateFileTransferServiceWith(d => d - .BeforeDownloadFile( - async () => - { - await tcpConnectionUtilities.RestartTcpConnection(); - - // Sleep to make the initial RPC call take longer than the allowed retry duration - await Task.Delay(retryDuration + TimeSpan.FromSeconds(1)); - - // Kill the first DownloadFile call to force the rpc call into retries - responseMessageTcpKiller.KillConnectionOnNextResponse(); - })) - .Build()) - .Build(CancellationToken); - - using var tempFile = new RandomTemporaryFileBuilder().Build(); - var inMemoryLog = new InMemoryLog(); - var executeScriptTask = clientAndTentacle.TentacleClient.DownloadFile(tempFile.File.FullName, CancellationToken, inMemoryLog); - - Func> action = async () => await executeScriptTask; - await action.Should().ThrowAsync(); - - recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).Started.Should().Be(0); - - inMemoryLog.ShouldHaveLoggedRetryFailureAndNoRetryAttempts(); - } - - class StopPortForwarderAfterFirstCallValues : IEnumerable - { - public IEnumerator GetEnumerator() - { - yield return true; - yield return false; - } - } - } -} \ No newline at end of file +// using System; +// using System.Collections; +// using System.Diagnostics; +// using System.IO; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils.Diagnostics; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; +// using Octopus.TestPortForwarder; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// /// +// /// These tests make sure that we can cancel or walk away (if code does not cooperate with cancellation tokens) +// /// from RPC calls when they are being retried and the rpc timeout period elapses. +// /// +// [IntegrationTestTimeout] +// public class ClientFileTransferRetriesTimeout : IntegrationTest +// { +// readonly TimeSpan retryIfRemainingDurationAtLeastBuffer = TimeSpan.FromSeconds(1); +// readonly TimeSpan retryBackoffBuffer = TimeSpan.FromSeconds(2); +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None, additionalParameterTypes: new object[] { typeof(StopPortForwarderAfterFirstCallValues) })] +// public async Task WhenRpcRetriesTimeOut_DuringUploadFile_TheRpcCallIsCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, bool stopPortForwarderAfterFirstCall) +// { +// PortForwarder portForwarder = null!; +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// // Set a short retry duration so we cancel fairly quickly +// .WithRetryDuration(TimeSpan.FromSeconds(15)) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var methodUsages) +// .DecorateFileTransferServiceWith(d => d +// .BeforeUploadFile( +// async () => +// { +// if (methodUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).LastException is null) +// { +// // Ensure there is an active connection so it can be killed correctly +// await tcpConnectionUtilities.RestartTcpConnection(); +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// else +// { +// if (stopPortForwarderAfterFirstCall) +// { +// // Kill the port forwarder so the next requests are in the connecting state when retries timeout +// Logger.Information("Killing PortForwarder"); +// portForwarder!.EnterKillNewAndExistingConnectionsMode(); +// } +// else +// { +// // Ensure there is an active connection so it can be killed correctly +// await tcpConnectionUtilities.RestartTcpConnection(); +// // Pause the port forwarder so the next requests are in-flight when retries timeout +// responseMessageTcpKiller.PauseConnectionOnNextResponse(); +// } +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// portForwarder = clientAndTentacle.PortForwarder; +// +// var inMemoryLog = new InMemoryLog(); +// +// var remotePath = Path.Combine(clientAndTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); +// var dataStream = DataStream.FromString("The Stream"); +// +// // Start the script which will wait for a file to exist +// var duration = Stopwatch.StartNew(); +// var executeScriptTask = clientAndTentacle.TentacleClient.UploadFile(remotePath, dataStream, CancellationToken, inMemoryLog); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// duration.Stop(); +// +// methodUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).Started.Should().BeGreaterOrEqualTo(2); +// methodUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).Started.Should().Be(0); +// +// // Ensure we actually waited and retried until the timeout policy kicked in +// duration.Elapsed.Should().BeGreaterOrEqualTo(clientAndTentacle.RpcRetrySettings.RetryDuration - retryIfRemainingDurationAtLeastBuffer - retryBackoffBuffer); +// +// inMemoryLog.ShouldHaveLoggedRetryAttemptsAndRetryFailure(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task WhenUploadFileFails_AndTakesLongerThanTheRetryDuration_TheCallIsNotRetried_AndTimesOut(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var retryDuration = TimeSpan.FromSeconds(15); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// // Set a short retry duration so we cancel fairly quickly +// .WithRetryDuration(retryDuration) +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var methodUsages) +// .DecorateFileTransferServiceWith(d => d +// .BeforeUploadFile( +// async () => +// { +// await tcpConnectionUtilities.RestartTcpConnection(); +// +// // Sleep to make the initial RPC call take longer than the allowed retry duration +// await Task.Delay(retryDuration + TimeSpan.FromSeconds(1)); +// +// // Kill the first UploadFile call to force the rpc call into retries +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// })) +// .Build()) +// .Build(CancellationToken); +// +// var inMemoryLog = new InMemoryLog(); +// var remotePath = Path.Combine(clientAndTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); +// var dataStream = DataStream.FromString("The Stream"); +// var executeScriptTask = clientAndTentacle.TentacleClient.UploadFile(remotePath, dataStream, CancellationToken, inMemoryLog); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// methodUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).Started.Should().Be(1); +// methodUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).Started.Should().Be(0); +// +// inMemoryLog.ShouldHaveLoggedRetryFailureAndNoRetryAttempts(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None, additionalParameterTypes: new object[] { typeof(StopPortForwarderAfterFirstCallValues) })] +// public async Task WhenRpcRetriesTimeOut_DuringDownloadFile_TheRpcCallIsCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, bool stopPortForwarderAfterFirstCall) +// { +// PortForwarder portForwarder = null!; +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// // Set a short retry duration so we cancel fairly quickly +// .WithRetryDuration(TimeSpan.FromSeconds(15)) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .DecorateFileTransferServiceWith(d => d +// .BeforeDownloadFile( +// async () => +// { +// if (recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).LastException is null) +// { +// // Ensure there is an active connection so it can be killed correctly +// await tcpConnectionUtilities.RestartTcpConnection(); +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// else +// { +// +// if (stopPortForwarderAfterFirstCall) +// { +// // Kill the port forwarder so the next requests are in the connecting state when retries timeout +// Logger.Information("Killing PortForwarder"); +// portForwarder!.Dispose(); +// } +// else +// { +// // Ensure there is an active connection so it can be killed correctly +// await tcpConnectionUtilities.RestartTcpConnection(); +// // Pause the port forwarder so the next requests are in-flight when retries timeout +// responseMessageTcpKiller.PauseConnectionOnNextResponse(); +// } +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// portForwarder = clientAndTentacle.PortForwarder; +// +// using var tempFile = new RandomTemporaryFileBuilder().Build(); +// +// var inMemoryLog = new InMemoryLog(); +// +// // Start the script which will wait for a file to exist +// var duration = Stopwatch.StartNew(); +// var executeScriptTask = clientAndTentacle.TentacleClient.DownloadFile(tempFile.File.FullName, CancellationToken, inMemoryLog); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// duration.Stop(); +// +// recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).Started.Should().BeGreaterOrEqualTo(2); +// recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).Started.Should().Be(0); +// +// // Ensure we actually waited and retried until the timeout policy kicked in +// duration.Elapsed.Should().BeGreaterOrEqualTo(clientAndTentacle.RpcRetrySettings.RetryDuration - retryIfRemainingDurationAtLeastBuffer - retryBackoffBuffer); +// +// inMemoryLog.ShouldHaveLoggedRetryAttemptsAndRetryFailure(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task WhenDownloadFileFails_AndTakesLongerThanTheRetryDuration_TheCallIsNotRetried_AndTimesOut(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var retryDuration = TimeSpan.FromSeconds(15); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// // Set a short retry duration so we cancel fairly quickly +// .WithRetryDuration(retryDuration) +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .DecorateFileTransferServiceWith(d => d +// .BeforeDownloadFile( +// async () => +// { +// await tcpConnectionUtilities.RestartTcpConnection(); +// +// // Sleep to make the initial RPC call take longer than the allowed retry duration +// await Task.Delay(retryDuration + TimeSpan.FromSeconds(1)); +// +// // Kill the first DownloadFile call to force the rpc call into retries +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// })) +// .Build()) +// .Build(CancellationToken); +// +// using var tempFile = new RandomTemporaryFileBuilder().Build(); +// var inMemoryLog = new InMemoryLog(); +// var executeScriptTask = clientAndTentacle.TentacleClient.DownloadFile(tempFile.File.FullName, CancellationToken, inMemoryLog); +// +// Func> action = async () => await executeScriptTask; +// await action.Should().ThrowAsync(); +// +// recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).Started.Should().Be(0); +// +// inMemoryLog.ShouldHaveLoggedRetryFailureAndNoRetryAttempts(); +// } +// +// class StopPortForwarderAfterFirstCallValues : IEnumerable +// { +// public IEnumerator GetEnumerator() +// { +// yield return true; +// yield return false; +// } +// } +// } +// } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientFileTransfersAreNotRetriedWhenRetriesAreDisabled.cs b/source/Octopus.Tentacle.Tests.Integration/ClientFileTransfersAreNotRetriedWhenRetriesAreDisabled.cs index b8ebd1261..4fd3d49c3 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientFileTransfersAreNotRetriedWhenRetriesAreDisabled.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientFileTransfersAreNotRetriedWhenRetriesAreDisabled.cs @@ -1,97 +1,97 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut; -using NUnit.Framework; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ClientFileTransfersAreNotRetriedWhenRetriesAreDisabled : IntegrationTest - { - [Test] - [TentacleConfigurations(testCommonVersions: true, scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task FailedUploadsAreNotRetriedAndFail(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithRetriesDisabled() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .DecorateFileTransferServiceWith(d => d - .BeforeUploadFile( - async () => - { - await tcpConnectionUtilities.RestartTcpConnection(); - - // Only kill the connection the first time, causing the upload - // to succeed - and therefore failing the test - if retries are attempted - if (recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).LastException is null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - })) - .Build()) - .Build(CancellationToken); - - var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); - var uploadFileTask = clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); - - await AssertionExtensions.Should(async () => await uploadFileTask).ThrowExceptionContractAsync(expectedException); - - recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).Started.Should().Be(1); - } - - [Test] - [TentacleConfigurations(testCommonVersions: true, scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task FailedDownloadsAreNotRetriedAndFail(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithRetriesDisabled() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .DecorateFileTransferServiceWith(d => d - .BeforeDownloadFile( - async () => - { - await tcpConnectionUtilities.RestartTcpConnection(); - - // Only kill the connection the first time, causing the upload - // to succeed - and therefore failing the test - if retries are attempted - if (recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).LastException is null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - })) - .Build()) - .Build(CancellationToken); - - var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); - - await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); - var downloadFileTask = clientTentacle.TentacleClient.DownloadFile(remotePath, CancellationToken); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); - - await AssertionExtensions.Should(async () => await downloadFileTask).ThrowExceptionContractAsync(expectedException); - - recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).Started.Should().Be(1); - } - } -} \ No newline at end of file +// using System; +// using System.IO; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut; +// using NUnit.Framework; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ClientFileTransfersAreNotRetriedWhenRetriesAreDisabled : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(testCommonVersions: true, scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task FailedUploadsAreNotRetriedAndFail(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithRetriesDisabled() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .DecorateFileTransferServiceWith(d => d +// .BeforeUploadFile( +// async () => +// { +// await tcpConnectionUtilities.RestartTcpConnection(); +// +// // Only kill the connection the first time, causing the upload +// // to succeed - and therefore failing the test - if retries are attempted +// if (recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).LastException is null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); +// var uploadFileTask = clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await uploadFileTask).ThrowExceptionContractAsync(expectedException); +// +// recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).Started.Should().Be(1); +// } +// +// [Test] +// [TentacleConfigurations(testCommonVersions: true, scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task FailedDownloadsAreNotRetriedAndFail(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithRetriesDisabled() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .DecorateFileTransferServiceWith(d => d +// .BeforeDownloadFile( +// async () => +// { +// await tcpConnectionUtilities.RestartTcpConnection(); +// +// // Only kill the connection the first time, causing the upload +// // to succeed - and therefore failing the test - if retries are attempted +// if (recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).LastException is null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); +// +// await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); +// var downloadFileTask = clientTentacle.TentacleClient.DownloadFile(remotePath, CancellationToken); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await downloadFileTask).ThrowExceptionContractAsync(expectedException); +// +// recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).Started.Should().Be(1); +// } +// } +// } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientFileTransfersAreRetriedWhenRetriesAreEnabled.cs b/source/Octopus.Tentacle.Tests.Integration/ClientFileTransfersAreRetriedWhenRetriesAreEnabled.cs index df282f0fd..a98a1c8fd 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientFileTransfersAreRetriedWhenRetriesAreEnabled.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientFileTransfersAreRetriedWhenRetriesAreEnabled.cs @@ -1,106 +1,106 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils; -using Octopus.Tentacle.CommonTestUtils.Diagnostics; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; - -namespace Octopus.Tentacle.Tests.Integration -{ - public class ClientFileTransfersAreRetriedWhenRetriesAreEnabled : IntegrationTest - { - [Test] - [TentacleConfigurations(testCommonVersions: true, scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task FailedUploadsAreRetriedAndIsEventuallySuccessful(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .DecorateFileTransferServiceWith(d => d - .BeforeUploadFile( - async () => - { - await tcpConnectionUtilities.RestartTcpConnection(); - - // Only kill the connection the first time, causing the upload - // to succeed - and therefore failing the test - if retries are attempted - if (recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).LastException is null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - })) - .Build()) - .Build(CancellationToken); - - var inMemoryLog = new InMemoryLog(); - - var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); - - var res = await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken, inMemoryLog); - res.Length.Should().Be(5); - - recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).Started.Should().Be(2); - - var downloadFile = await clientTentacle.TentacleClient.DownloadFile(remotePath, CancellationToken); - var actuallySent = await downloadFile.GetUtf8String(CancellationToken); - actuallySent.Should().Be("Hello"); - - inMemoryLog.ShouldHaveLoggedRetryAttemptsAndNoRetryFailures(); - } - - [Test] - [TentacleConfigurations(testCommonVersions: true, scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task FailedDownloadsAreRetriedAndIsEventuallySuccessful(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .DecorateFileTransferServiceWith(d => d - .BeforeDownloadFile( - async () => - { - await tcpConnectionUtilities.RestartTcpConnection(); - - // Only kill the connection the first time, causing the upload - // to succeed - and therefore failing the test - if retries are attempted - if (recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).LastException is null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - })) - .Build()) - .Build(CancellationToken); - - var inMemoryLog = new InMemoryLog(); - - var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); - - await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); - var downloadFile = await clientTentacle.TentacleClient.DownloadFile(remotePath, CancellationToken, inMemoryLog); - var actuallySent = await downloadFile.GetUtf8String(CancellationToken); - - recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).Started.Should().Be(2); - - actuallySent.Should().Be("Hello"); - - inMemoryLog.ShouldHaveLoggedRetryAttemptsAndNoRetryFailures(); - } - } -} +// using System; +// using System.IO; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils; +// using Octopus.Tentacle.CommonTestUtils.Diagnostics; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// public class ClientFileTransfersAreRetriedWhenRetriesAreEnabled : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(testCommonVersions: true, scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task FailedUploadsAreRetriedAndIsEventuallySuccessful(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .DecorateFileTransferServiceWith(d => d +// .BeforeUploadFile( +// async () => +// { +// await tcpConnectionUtilities.RestartTcpConnection(); +// +// // Only kill the connection the first time, causing the upload +// // to succeed - and therefore failing the test - if retries are attempted +// if (recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).LastException is null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var inMemoryLog = new InMemoryLog(); +// +// var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); +// +// var res = await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken, inMemoryLog); +// res.Length.Should().Be(5); +// +// recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientFileTransferService.UploadFileAsync)).Started.Should().Be(2); +// +// var downloadFile = await clientTentacle.TentacleClient.DownloadFile(remotePath, CancellationToken); +// var actuallySent = await downloadFile.GetUtf8String(CancellationToken); +// actuallySent.Should().Be("Hello"); +// +// inMemoryLog.ShouldHaveLoggedRetryAttemptsAndNoRetryFailures(); +// } +// +// [Test] +// [TentacleConfigurations(testCommonVersions: true, scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task FailedDownloadsAreRetriedAndIsEventuallySuccessful(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .DecorateFileTransferServiceWith(d => d +// .BeforeDownloadFile( +// async () => +// { +// await tcpConnectionUtilities.RestartTcpConnection(); +// +// // Only kill the connection the first time, causing the upload +// // to succeed - and therefore failing the test - if retries are attempted +// if (recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).LastException is null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var inMemoryLog = new InMemoryLog(); +// +// var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); +// +// await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); +// var downloadFile = await clientTentacle.TentacleClient.DownloadFile(remotePath, CancellationToken, inMemoryLog); +// var actuallySent = await downloadFile.GetUtf8String(CancellationToken); +// +// recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientFileTransferService.DownloadFileAsync)).Started.Should().Be(2); +// +// actuallySent.Should().Be("Hello"); +// +// inMemoryLog.ShouldHaveLoggedRetryAttemptsAndNoRetryFailures(); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientGathersRpcCallMetrics.cs b/source/Octopus.Tentacle.Tests.Integration/ClientGathersRpcCallMetrics.cs index 5975f8ded..4c76806fc 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientGathersRpcCallMetrics.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientGathersRpcCallMetrics.cs @@ -1,227 +1,227 @@ -using System; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.Capabilities; -using Octopus.Tentacle.Contracts.Observability; -using Octopus.Tentacle.Contracts.ScriptServiceV2; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ClientGathersRpcCallMetrics : IntegrationTest - { - [Test] - [TentacleConfigurations(testCommonVersions: true)] - public async Task ExecuteScriptShouldGatherMetrics_WhenSucceeds(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - // Arrange - var tentacleClientObserver = new TestTentacleClientObserver(); - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleClientObserver(tentacleClientObserver) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b.Print("Hello")) - .Build(); - - // Act - var (finalResponse, _) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - - // Assert - finalResponse.State.Should().Be(ProcessState.Complete); - - var executeScriptMetrics = tentacleClientObserver.ExecuteScriptMetrics.Should().ContainSingle().Subject; - ThenClientOperationMetricsShouldBeSuccessful(executeScriptMetrics); - - var expectedScriptService = tentacleConfigurationTestCase.ScriptServiceToTest == TentacleConfigurationTestCases.ScriptServiceV2Type - ? nameof(IScriptServiceV2) - : nameof(IScriptService); - - tentacleClientObserver.RpcCallMetrics.Should().NotBeEmpty(); - tentacleClientObserver.RpcCallMetrics.Should().ContainSingle(m => m.RpcCall.Name == nameof(ICapabilitiesServiceV2.GetCapabilities) && m.RpcCall.Service == nameof(ICapabilitiesServiceV2)); - tentacleClientObserver.RpcCallMetrics.Should().ContainSingle(m => m.RpcCall.Name == nameof(IScriptServiceV2.StartScript) && m.RpcCall.Service == expectedScriptService); - tentacleClientObserver.RpcCallMetrics.Should().Contain(m => m.RpcCall.Name == nameof(IScriptServiceV2.GetStatus) && m.RpcCall.Service == expectedScriptService); - tentacleClientObserver.RpcCallMetrics.Should().ContainSingle(m => m.RpcCall.Name == nameof(IScriptServiceV2.CompleteScript) && m.RpcCall.Service == expectedScriptService); - tentacleClientObserver.RpcCallMetrics.Should().AllSatisfy(m => m.Succeeded.Should().BeTrue()); - } - - [Test] - [TentacleConfigurations(testCommonVersions: true)] - public async Task ExecuteScriptShouldGatherMetrics_WhenFails(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - // Arrange - var tentacleClientObserver = new TestTentacleClientObserver(); - var exception = new HalibutClientException("Error"); - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleClientObserver(tentacleClientObserver) - .WithRetryDuration(TimeSpan.FromSeconds(1)) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .DecorateAllScriptServicesWith(u => u - .BeforeStartScript( - () => throw exception)) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b.Print("Hello")) - .Build(); - - // Act - await AssertionExtensions.Should(() => clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken)).ThrowAsync(); - - // Assert - var executeScriptMetrics = tentacleClientObserver.ExecuteScriptMetrics.Should().ContainSingle().Subject; - ThenClientOperationMetricsShouldBeFailed(executeScriptMetrics, exception); - - tentacleClientObserver.RpcCallMetrics.Should().NotBeEmpty(); - var startScriptMetric = tentacleClientObserver.RpcCallMetrics.Should().ContainSingle(m => m.RpcCall.Name == "StartScript").Subject; - startScriptMetric.Succeeded.Should().BeFalse(); - } - - [Test] - [TentacleConfigurations(testCommonVersions: true)] - public async Task UploadFileShouldGatherMetrics_WhenSucceeds(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - // Arrange - var tentacleClientObserver = new TestTentacleClientObserver(); - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleClientObserver(tentacleClientObserver) - .Build(CancellationToken); - - var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); - - // Act - await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); - - // Assert - var uploadFileMetrics = tentacleClientObserver.UploadFileMetrics.Should().ContainSingle().Subject; - ThenClientOperationMetricsShouldBeSuccessful(uploadFileMetrics); - - tentacleClientObserver.RpcCallMetrics.Should().HaveCountGreaterThan(0); - var metric = tentacleClientObserver.RpcCallMetrics.Last(); - metric.RpcCall.Name.Should().Be(nameof(IFileTransferService.UploadFile)); - metric.RpcCall.Service.Should().Be(nameof(IFileTransferService)); - metric.Succeeded.Should().BeTrue(); - } - - [Test] - [TentacleConfigurations(testCommonVersions: true)] - public async Task UploadFileShouldGatherMetrics_WhenFails(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - // Arrange - var tentacleClientObserver = new TestTentacleClientObserver(); - var exception = new HalibutClientException("Error"); - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleClientObserver(tentacleClientObserver) - .WithRetryDuration(TimeSpan.FromSeconds(1)) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .DecorateFileTransferServiceWith(d => d.BeforeUploadFile(() => throw exception)) - .Build()) - .Build(CancellationToken); - - var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); - - // Act - await AssertionExtensions.Should(() => clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken)).ThrowAsync(); - - // Assert - var uploadFileMetrics = tentacleClientObserver.UploadFileMetrics.Should().ContainSingle().Subject; - ThenClientOperationMetricsShouldBeFailed(uploadFileMetrics, exception); - - tentacleClientObserver.RpcCallMetrics.Should().NotBeEmpty(); - var metric = tentacleClientObserver.RpcCallMetrics.Last(); - metric.RpcCall.Name.Should().Be(nameof(IFileTransferService.UploadFile)); - metric.RpcCall.Service.Should().Be(nameof(IFileTransferService)); - metric.Succeeded.Should().BeFalse(); - } - - [Test] - [TentacleConfigurations(testCommonVersions: true)] - public async Task DownloadFileShouldGatherMetrics_WhenSucceeds(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - // Arrange - var tentacleClientObserver = new TestTentacleClientObserver(); - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleClientObserver(tentacleClientObserver) - .Build(CancellationToken); - - var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "DownloadFile.txt"); - await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); - - // Act - await clientTentacle.TentacleClient.DownloadFile(remotePath, CancellationToken); - - // Assert - var downloadFileMetrics = tentacleClientObserver.DownloadFileMetrics.Should().ContainSingle().Subject; - ThenClientOperationMetricsShouldBeSuccessful(downloadFileMetrics); - - tentacleClientObserver.RpcCallMetrics.Should().HaveCountGreaterThan(1); // the first one will be the upload - var metric = tentacleClientObserver.RpcCallMetrics.Last(); - metric.RpcCall.Name.Should().Be(nameof(IFileTransferService.DownloadFile)); - metric.RpcCall.Service.Should().Be(nameof(IFileTransferService)); - metric.Succeeded.Should().BeTrue(); - } - - [Test] - [TentacleConfigurations(testCommonVersions: true)] - public async Task DownloadFileShouldGatherMetrics_WhenFails(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - // Arrange - var tentacleClientObserver = new TestTentacleClientObserver(); - var exception = new HalibutClientException("Error"); - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleClientObserver(tentacleClientObserver) - .WithRetryDuration(TimeSpan.FromSeconds(1)) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .DecorateFileTransferServiceWith(d => d.BeforeDownloadFile(() => throw exception)) - .Build()) - .Build(CancellationToken); - - var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "DownloadFile.txt"); - await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); - - // Act - await AssertionExtensions.Should(() => clientTentacle.TentacleClient.DownloadFile(remotePath, CancellationToken)).ThrowAsync(); - - // Assert - var downloadFileMetrics = tentacleClientObserver.DownloadFileMetrics.Should().ContainSingle().Subject; - ThenClientOperationMetricsShouldBeFailed(downloadFileMetrics, exception); - - tentacleClientObserver.RpcCallMetrics.Should().HaveCountGreaterThan(1); // the first one will be the upload - var metric = tentacleClientObserver.RpcCallMetrics.Last(); - metric.RpcCall.Name.Should().Be(nameof(IFileTransferService.DownloadFile)); - metric.RpcCall.Service.Should().Be(nameof(IFileTransferService)); - metric.Succeeded.Should().BeFalse(); - } - - private static void ThenClientOperationMetricsShouldBeSuccessful(ClientOperationMetrics metric) - { - metric.Succeeded.Should().BeTrue(); - metric.Exception.Should().BeNull(); - metric.WasCancelled.Should().BeFalse(); - - metric.End.Should().BeOnOrAfter(metric.Start); - metric.Duration.Should().Be(metric.End - metric.Start); - } - - private static void ThenClientOperationMetricsShouldBeFailed(ClientOperationMetrics metric, Exception expectedException) - { - metric.Succeeded.Should().BeFalse(); - metric.Exception.Should().BeEquivalentTo(expectedException); - metric.WasCancelled.Should().BeFalse(); - - metric.End.Should().BeOnOrAfter(metric.Start); - metric.Duration.Should().Be(metric.End - metric.Start); - } - } -} \ No newline at end of file +// using System; +// using System.IO; +// using System.Linq; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Contracts.Capabilities; +// using Octopus.Tentacle.Contracts.Observability; +// using Octopus.Tentacle.Contracts.ScriptServiceV2; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ClientGathersRpcCallMetrics : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(testCommonVersions: true)] +// public async Task ExecuteScriptShouldGatherMetrics_WhenSucceeds(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// // Arrange +// var tentacleClientObserver = new TestTentacleClientObserver(); +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleClientObserver(tentacleClientObserver) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b.Print("Hello")) +// .Build(); +// +// // Act +// var (finalResponse, _) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// +// // Assert +// finalResponse.State.Should().Be(ProcessState.Complete); +// +// var executeScriptMetrics = tentacleClientObserver.ExecuteScriptMetrics.Should().ContainSingle().Subject; +// ThenClientOperationMetricsShouldBeSuccessful(executeScriptMetrics); +// +// var expectedScriptService = tentacleConfigurationTestCase.ScriptServiceToTest == TentacleConfigurationTestCases.ScriptServiceV2Type +// ? nameof(IScriptServiceV2) +// : nameof(IScriptService); +// +// tentacleClientObserver.RpcCallMetrics.Should().NotBeEmpty(); +// tentacleClientObserver.RpcCallMetrics.Should().ContainSingle(m => m.RpcCall.Name == nameof(ICapabilitiesServiceV2.GetCapabilities) && m.RpcCall.Service == nameof(ICapabilitiesServiceV2)); +// tentacleClientObserver.RpcCallMetrics.Should().ContainSingle(m => m.RpcCall.Name == nameof(IScriptServiceV2.StartScript) && m.RpcCall.Service == expectedScriptService); +// tentacleClientObserver.RpcCallMetrics.Should().Contain(m => m.RpcCall.Name == nameof(IScriptServiceV2.GetStatus) && m.RpcCall.Service == expectedScriptService); +// tentacleClientObserver.RpcCallMetrics.Should().ContainSingle(m => m.RpcCall.Name == nameof(IScriptServiceV2.CompleteScript) && m.RpcCall.Service == expectedScriptService); +// tentacleClientObserver.RpcCallMetrics.Should().AllSatisfy(m => m.Succeeded.Should().BeTrue()); +// } +// +// [Test] +// [TentacleConfigurations(testCommonVersions: true)] +// public async Task ExecuteScriptShouldGatherMetrics_WhenFails(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// // Arrange +// var tentacleClientObserver = new TestTentacleClientObserver(); +// var exception = new HalibutClientException("Error"); +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleClientObserver(tentacleClientObserver) +// .WithRetryDuration(TimeSpan.FromSeconds(1)) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .DecorateAllScriptServicesWith(u => u +// .BeforeStartScript( +// () => throw exception)) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b.Print("Hello")) +// .Build(); +// +// // Act +// await AssertionExtensions.Should(() => clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken)).ThrowAsync(); +// +// // Assert +// var executeScriptMetrics = tentacleClientObserver.ExecuteScriptMetrics.Should().ContainSingle().Subject; +// ThenClientOperationMetricsShouldBeFailed(executeScriptMetrics, exception); +// +// tentacleClientObserver.RpcCallMetrics.Should().NotBeEmpty(); +// var startScriptMetric = tentacleClientObserver.RpcCallMetrics.Should().ContainSingle(m => m.RpcCall.Name == "StartScript").Subject; +// startScriptMetric.Succeeded.Should().BeFalse(); +// } +// +// [Test] +// [TentacleConfigurations(testCommonVersions: true)] +// public async Task UploadFileShouldGatherMetrics_WhenSucceeds(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// // Arrange +// var tentacleClientObserver = new TestTentacleClientObserver(); +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleClientObserver(tentacleClientObserver) +// .Build(CancellationToken); +// +// var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); +// +// // Act +// await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); +// +// // Assert +// var uploadFileMetrics = tentacleClientObserver.UploadFileMetrics.Should().ContainSingle().Subject; +// ThenClientOperationMetricsShouldBeSuccessful(uploadFileMetrics); +// +// tentacleClientObserver.RpcCallMetrics.Should().HaveCountGreaterThan(0); +// var metric = tentacleClientObserver.RpcCallMetrics.Last(); +// metric.RpcCall.Name.Should().Be(nameof(IFileTransferService.UploadFile)); +// metric.RpcCall.Service.Should().Be(nameof(IFileTransferService)); +// metric.Succeeded.Should().BeTrue(); +// } +// +// [Test] +// [TentacleConfigurations(testCommonVersions: true)] +// public async Task UploadFileShouldGatherMetrics_WhenFails(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// // Arrange +// var tentacleClientObserver = new TestTentacleClientObserver(); +// var exception = new HalibutClientException("Error"); +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleClientObserver(tentacleClientObserver) +// .WithRetryDuration(TimeSpan.FromSeconds(1)) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .DecorateFileTransferServiceWith(d => d.BeforeUploadFile(() => throw exception)) +// .Build()) +// .Build(CancellationToken); +// +// var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); +// +// // Act +// await AssertionExtensions.Should(() => clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken)).ThrowAsync(); +// +// // Assert +// var uploadFileMetrics = tentacleClientObserver.UploadFileMetrics.Should().ContainSingle().Subject; +// ThenClientOperationMetricsShouldBeFailed(uploadFileMetrics, exception); +// +// tentacleClientObserver.RpcCallMetrics.Should().NotBeEmpty(); +// var metric = tentacleClientObserver.RpcCallMetrics.Last(); +// metric.RpcCall.Name.Should().Be(nameof(IFileTransferService.UploadFile)); +// metric.RpcCall.Service.Should().Be(nameof(IFileTransferService)); +// metric.Succeeded.Should().BeFalse(); +// } +// +// [Test] +// [TentacleConfigurations(testCommonVersions: true)] +// public async Task DownloadFileShouldGatherMetrics_WhenSucceeds(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// // Arrange +// var tentacleClientObserver = new TestTentacleClientObserver(); +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleClientObserver(tentacleClientObserver) +// .Build(CancellationToken); +// +// var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "DownloadFile.txt"); +// await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); +// +// // Act +// await clientTentacle.TentacleClient.DownloadFile(remotePath, CancellationToken); +// +// // Assert +// var downloadFileMetrics = tentacleClientObserver.DownloadFileMetrics.Should().ContainSingle().Subject; +// ThenClientOperationMetricsShouldBeSuccessful(downloadFileMetrics); +// +// tentacleClientObserver.RpcCallMetrics.Should().HaveCountGreaterThan(1); // the first one will be the upload +// var metric = tentacleClientObserver.RpcCallMetrics.Last(); +// metric.RpcCall.Name.Should().Be(nameof(IFileTransferService.DownloadFile)); +// metric.RpcCall.Service.Should().Be(nameof(IFileTransferService)); +// metric.Succeeded.Should().BeTrue(); +// } +// +// [Test] +// [TentacleConfigurations(testCommonVersions: true)] +// public async Task DownloadFileShouldGatherMetrics_WhenFails(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// // Arrange +// var tentacleClientObserver = new TestTentacleClientObserver(); +// var exception = new HalibutClientException("Error"); +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleClientObserver(tentacleClientObserver) +// .WithRetryDuration(TimeSpan.FromSeconds(1)) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .DecorateFileTransferServiceWith(d => d.BeforeDownloadFile(() => throw exception)) +// .Build()) +// .Build(CancellationToken); +// +// var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "DownloadFile.txt"); +// await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); +// +// // Act +// await AssertionExtensions.Should(() => clientTentacle.TentacleClient.DownloadFile(remotePath, CancellationToken)).ThrowAsync(); +// +// // Assert +// var downloadFileMetrics = tentacleClientObserver.DownloadFileMetrics.Should().ContainSingle().Subject; +// ThenClientOperationMetricsShouldBeFailed(downloadFileMetrics, exception); +// +// tentacleClientObserver.RpcCallMetrics.Should().HaveCountGreaterThan(1); // the first one will be the upload +// var metric = tentacleClientObserver.RpcCallMetrics.Last(); +// metric.RpcCall.Name.Should().Be(nameof(IFileTransferService.DownloadFile)); +// metric.RpcCall.Service.Should().Be(nameof(IFileTransferService)); +// metric.Succeeded.Should().BeFalse(); +// } +// +// private static void ThenClientOperationMetricsShouldBeSuccessful(ClientOperationMetrics metric) +// { +// metric.Succeeded.Should().BeTrue(); +// metric.Exception.Should().BeNull(); +// metric.WasCancelled.Should().BeFalse(); +// +// metric.End.Should().BeOnOrAfter(metric.Start); +// metric.Duration.Should().Be(metric.End - metric.Start); +// } +// +// private static void ThenClientOperationMetricsShouldBeFailed(ClientOperationMetrics metric, Exception expectedException) +// { +// metric.Succeeded.Should().BeFalse(); +// metric.Exception.Should().BeEquivalentTo(expectedException); +// metric.WasCancelled.Should().BeFalse(); +// +// metric.End.Should().BeOnOrAfter(metric.Start); +// metric.Duration.Should().Be(metric.End - metric.Start); +// } +// } +// } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionAdditionalScripts.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionAdditionalScripts.cs index 24e6ea8ca..27e1c6d60 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionAdditionalScripts.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionAdditionalScripts.cs @@ -1,50 +1,50 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ClientScriptExecutionAdditionalScripts : IntegrationTest - { - [Test] - [TentacleConfigurations(testCommonVersions: true)] - public async Task AdditionalScriptsWork(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - using var tmp = new TemporaryDirectory(); - var path = Path.Combine(tmp.DirectoryPath, "file"); - - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .Build(CancellationToken); - - var scriptBuilder = new ScriptBuilder() - .CreateFile(path) // How files are made are different in bash and powershell, doing this ensures the client and tentacle really are using the correct script. - .Print("Hello"); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .WithAdditionalScriptType(ScriptType.Bash, scriptBuilder.BuildBashScript()) - // Additional Scripts don't actually work on tentacle for anything other than bash. - // Below is what we would have expected to tentacle to work with. - //.WithAdditionalScriptTypes(ScriptType.PowerShell, scriptBuilder.BuildPowershellScript()) - // But instead we need to send the powershell in the scriptbody. - .WithScriptBody(scriptBuilder.BuildPowershellScript()) - .Build(); - - var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - - finalResponse.State.Should().Be(ProcessState.Complete); - finalResponse.ExitCode.Should().Be(0); - - var allLogs = logs.JoinLogs(); - - allLogs.Should().Contain("Hello"); - } - } -} +// using System; +// using System.IO; +// using System.Threading.Tasks; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ClientScriptExecutionAdditionalScripts : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(testCommonVersions: true)] +// public async Task AdditionalScriptsWork(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// using var tmp = new TemporaryDirectory(); +// var path = Path.Combine(tmp.DirectoryPath, "file"); +// +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .Build(CancellationToken); +// +// var scriptBuilder = new ScriptBuilder() +// .CreateFile(path) // How files are made are different in bash and powershell, doing this ensures the client and tentacle really are using the correct script. +// .Print("Hello"); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .WithAdditionalScriptType(ScriptType.Bash, scriptBuilder.BuildBashScript()) +// // Additional Scripts don't actually work on tentacle for anything other than bash. +// // Below is what we would have expected to tentacle to work with. +// //.WithAdditionalScriptTypes(ScriptType.PowerShell, scriptBuilder.BuildPowershellScript()) +// // But instead we need to send the powershell in the scriptbody. +// .WithScriptBody(scriptBuilder.BuildPowershellScript()) +// .Build(); +// +// var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// +// finalResponse.State.Should().Be(ProcessState.Complete); +// finalResponse.ExitCode.Should().Be(0); +// +// var allLogs = logs.JoinLogs(); +// +// allLogs.Should().Contain("Hello"); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreDisabled.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreDisabled.cs index 91aedbf7e..472a9893a 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreDisabled.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreDisabled.cs @@ -1,434 +1,434 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut; -using NUnit.Framework; -using Octopus.Tentacle.Client.Scripts; -using Octopus.Tentacle.Client.Scripts.Models; -using Octopus.Tentacle.CommonTestUtils.Builders; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.Capabilities; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators.Proxies; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Util.PendingRequestQueueHelpers; -using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; -using Octopus.Tentacle.Util; -using Octopus.TestPortForwarder; - -namespace Octopus.Tentacle.Tests.Integration -{ - /// - /// These tests make sure that we can cancel the ExecuteScript operation when using Tentacle Client with RPC retries disabled. - /// - [IntegrationTestTimeout] - public class ClientScriptExecutionCanBeCancelledWhenRetriesAreDisabled : IntegrationTest - { - [Test] - [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage) })] - public async Task DuringGetCapabilities_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) - { - // ARRANGE - var rpcCallHasStarted = new Reference(false); - var hasPausedOrStoppedPortForwarder = false; - var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue - .WithServiceEndpointModifier(point => point.TryAndConnectForALongTime()) - .WithRetriesDisabled() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithPortForwarder(out var portForwarder) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var capabilitiesMethodUsages) - .RecordMethodUsages(tentacleConfigurationTestCase, out var scriptMethodUsages) - .DecorateCapabilitiesServiceV2With(d => d - .BeforeGetCapabilities( - async () => - { - if (!hasPausedOrStoppedPortForwarder) - { - hasPausedOrStoppedPortForwarder = true; - await tcpConnectionUtilities.RestartTcpConnection(); - - PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); - if (rpcCallStage == RpcCallStage.Connecting) - { - await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); - } - } - - ensureCancellationOccursDuringAnRpcCall.Release(); - }) - .AfterGetCapabilities( - async _ => - { - await ensureCancellationOccursDuringAnRpcCall.WaitAsync(CancellationToken); - })) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("Should not run this script") - .Sleep(TimeSpan.FromHours(1))) - .Build(); - - // ACT - var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); - - // ASSERT - // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring - capabilitiesMethodUsages.ForGetCapabilitiesAsync().LastException.Should().BeRequestCancelledException(rpcCallStage); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - actualException.ShouldMatchExceptionContract(expectedException); - - cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(15)); - - capabilitiesMethodUsages.ForGetCapabilitiesAsync().Started.Should().Be(1); - scriptMethodUsages.ForStartScriptAsync().Started.Should().Be(0, "Should not have proceeded past GetCapabilities"); - scriptMethodUsages.ForCancelScriptAsync().Started.Should().Be(0, "Should not have tried to call CancelScript"); - } - - [Test] - [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage) })] - public async Task DuringStartScript_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) - { - // ARRANGE - var rpcCallHasStarted = new Reference(false); - var restartedPortForwarderForCancel = false; - var hasPausedOrStoppedPortForwarder = false; - var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue - .WithServiceEndpointModifier(point => point.TryAndConnectForALongTime()) - .WithRetriesDisabled() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithPortForwarder(out var portForwarder) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeStartScript( - async () => - { - if (!hasPausedOrStoppedPortForwarder) - { - hasPausedOrStoppedPortForwarder = true; - await tcpConnectionUtilities.RestartTcpConnection(); - PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); - if (rpcCallStage == RpcCallStage.Connecting) - { - await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); - } - } - - ensureCancellationOccursDuringAnRpcCall.Release(); - }) - .BeforeCancelScript( - async () => - { - await Task.CompletedTask; - - if (!restartedPortForwarderForCancel) - { - restartedPortForwarderForCancel = true; - UnPauseOrRestartPortForwarder(tentacleConfigurationTestCase.TentacleType, rpcCallStage, portForwarder); - } - })) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("The script") - .Sleep(TimeSpan.FromHours(1))) - .Build(); - - // ACT - var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); - - // ASSERT - // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring - recordedUsages.ForStartScriptAsync().LastException.Should().BeRequestCancelledException(rpcCallStage); - - if (rpcCallStage == RpcCallStage.Connecting) - { - // Should have cancelled the RPC call and exited immediately - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - - actualException.ShouldMatchExceptionContract(expectedException); - - // We should have cancelled the RPC call quickly and existed - cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(15)); - - recordedUsages.ForStartScriptAsync().Started.Should().Be(1); - recordedUsages.ForGetStatusAsync().Started.Should().Be(0, "Test should not have not proceeded past StartScript before being Cancelled"); - recordedUsages.ForCancelScriptAsync().Started.Should().Be(0); - recordedUsages.ForCompleteScriptAsync().Started.Should().Be(0); - } - else // Transferring - { - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - actualException.ShouldMatchExceptionContract(expectedException); - - // Assert the CancelScript and CompleteScript flow happened fairly quickly - cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); - - recordedUsages.ForStartScriptAsync().Started.Should().Be(1); - recordedUsages.ForGetStatusAsync().Started.Should().Be(0, "Test should not have not proceeded past StartScript before being Cancelled"); - recordedUsages.ForCancelScriptAsync().Started.Should().BeGreaterOrEqualTo(1); - recordedUsages.ForCompleteScriptAsync().Started.Should().Be(1); - } - } - - [Test] - [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage) })] - public async Task DuringGetStatus_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) - { - // ARRANGE - var rpcCallHasStarted = new Reference(false); - var restartedPortForwarderForCancel = false; - var hasPausedOrStoppedPortForwarder = false; - var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue - .WithServiceEndpointModifier(point => point.TryAndConnectForALongTime()) - .WithRetriesDisabled() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithPortForwarder(out var portForwarder) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeGetStatus( - async () => - { - if (!hasPausedOrStoppedPortForwarder) - { - hasPausedOrStoppedPortForwarder = true; - await tcpConnectionUtilities.RestartTcpConnection(); - PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); - if (rpcCallStage == RpcCallStage.Connecting) - { - await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); - } - } - - ensureCancellationOccursDuringAnRpcCall.Release(); - }) - .AfterGetStatus( - async () => - { - await ensureCancellationOccursDuringAnRpcCall.WaitAsync(CancellationToken); - }) - .BeforeCancelScript( - async () => - { - await Task.CompletedTask; - - if (!restartedPortForwarderForCancel) - { - restartedPortForwarderForCancel = true; - UnPauseOrRestartPortForwarder(tentacleConfigurationTestCase.TentacleType, rpcCallStage, portForwarder); - } - })) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("The script") - .Sleep(TimeSpan.FromHours(1))) - .Build(); - - // ACT - var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); - - // ASSERT - // Assert that script execution was cancelled - actualException.Should().BeScriptExecutionCancelledException(); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - actualException.ShouldMatchExceptionContract(expectedException); - - // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring - recordedUsages.ForGetStatusAsync().LastException.Should().BeRequestCancelledException(rpcCallStage); - - // Assert the CancelScript and CompleteScript flow happened fairly quickly - cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); - - // The expected RPC calls were made - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().BeGreaterOrEqualTo(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); - } - - [Test] - [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage) })] - public async Task DuringCompleteScript_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) - { - // ARRANGE - var rpcCallHasStarted = new Reference(false); - var hasPausedOrStoppedPortForwarder = false; - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue - .WithServiceEndpointModifier(point => point.TryAndConnectForALongTime()) - .WithRetriesDisabled() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithPortForwarder(out var portForwarder) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeCompleteScript( - async () => - { - if (!hasPausedOrStoppedPortForwarder) - { - hasPausedOrStoppedPortForwarder = true; - await tcpConnectionUtilities.RestartTcpConnection(); - PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); - if (rpcCallStage == RpcCallStage.Connecting) - { - await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); - } - } - })) - .Build()) - .Build(CancellationToken); - - clientAndTentacle.TentacleClient.OnCancellationAbandonCompleteScriptAfter = TimeSpan.FromSeconds(20); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("The script") - .Sleep(TimeSpan.FromSeconds(5))) - .Build(); - - // ACT - var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, new SemaphoreSlim(int.MaxValue, int.MaxValue)); - - // ASSERT - // The actual exception may be null if the script completed before the cancellation was observed - if (actualException != null) - { - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - actualException.ShouldMatchExceptionContract(expectedException); - } - - // Halibut Errors were recorded on CompleteScript - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).LastException?.Should().Match(x => x is HalibutClientException || x is OperationCanceledException || x is TaskCanceledException); // Complete Script was cancelled quickly - cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); - - // The expected RPC calls were made - recordedUsages.ForStartScriptAsync().Started.Should().Be(1); - recordedUsages.ForGetStatusAsync().Started.Should().BeGreaterThanOrEqualTo(1); - recordedUsages.ForCancelScriptAsync().Started.Should().Be(0); - recordedUsages.ForCompleteScriptAsync().Started.Should().BeGreaterOrEqualTo(1); - } - - void PauseOrStopPortForwarder(RpcCallStage rpcCallStage, PortForwarder portForwarder, IResponseMessageTcpKiller responseMessageTcpKiller, Reference rpcCallHasStarted) - { - if (rpcCallStage == RpcCallStage.Connecting) - { - Logger.Information("Killing the port forwarder so the next RPCs are in the connecting state when being cancelled"); - //portForwarder.Stop(); - portForwarder.EnterKillNewAndExistingConnectionsMode(); - rpcCallHasStarted.Value = true; - } - else - { - Logger.Information("Will Pause the port forwarder on next response so the next RPC is in-flight when being cancelled"); - responseMessageTcpKiller.PauseConnectionOnNextResponse(() => rpcCallHasStarted.Value = true); - } - } - - void UnPauseOrRestartPortForwarder(TentacleType tentacleType, RpcCallStage rpcCallStage, Reference portForwarder) - { - if (rpcCallStage == RpcCallStage.Connecting) - { - Logger.Information("Starting the PortForwarder as we stopped it to get the StartScript RPC call in the Connecting state"); - //portForwarder.Value.Start(); - portForwarder.Value.ReturnToNormalMode(); - } - else if (tentacleType == TentacleType.Polling) - { - Logger.Information("UnPausing the PortForwarder as we paused the connections which means Polling will be stalled"); - portForwarder.Value.UnPauseExistingConnections(); - portForwarder.Value.CloseExistingConnections(); - } - } - - async Task<(ScriptExecutionResult response, Exception? actualException, TimeSpan cancellationDuration)> ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted( - ClientAndTentacle clientAndTentacle, - ExecuteScriptCommand executeScriptCommand, - Reference rpcCallHasStarted, - SemaphoreSlim whenTheRequestCanBeCancelled) - { - Logger.Information("Start of ExecuteScriptThenCancel"); - var cancelExecutionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); - - var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript( - executeScriptCommand, - cancelExecutionCancellationTokenSource.Token); - - Logger.Information("Create action"); - Func)>> action = async () => await executeScriptTask; - - Logger.Information("Waiting for the RPC Call to start"); - await Wait.For( - () => rpcCallHasStarted.Value, - TimeSpan.FromSeconds(30), - () => throw new Exception("RPC call did not start") - ,CancellationToken); - Logger.Information("RPC Call has start"); - - await Task.Delay(TimeSpan.FromSeconds(6), CancellationToken); - - var cancellationDuration = new Stopwatch(); - await whenTheRequestCanBeCancelled.WithLockAsync(() => - { - Logger.Information("Cancelling ExecuteScript"); - cancelExecutionCancellationTokenSource.Cancel(); - cancellationDuration.Start(); - }, CancellationToken); - - Exception? actualException = null; - (ScriptExecutionResult Response, List Logs)? responseAndLogs = null; - try - { - responseAndLogs = await action(); - } - catch (Exception ex) - { - actualException = ex; - } - - cancellationDuration.Stop(); - return (responseAndLogs?.Response, actualException, cancellationDuration.Elapsed); - } - } -} \ No newline at end of file +// using System; +// using System.Collections.Generic; +// using System.Diagnostics; +// using System.Threading; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut; +// using NUnit.Framework; +// using Octopus.Tentacle.Client.Scripts; +// using Octopus.Tentacle.Client.Scripts.Models; +// using Octopus.Tentacle.CommonTestUtils.Builders; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Contracts.Capabilities; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators.Proxies; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Util.PendingRequestQueueHelpers; +// using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; +// using Octopus.Tentacle.Util; +// using Octopus.TestPortForwarder; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// /// +// /// These tests make sure that we can cancel the ExecuteScript operation when using Tentacle Client with RPC retries disabled. +// /// +// [IntegrationTestTimeout] +// public class ClientScriptExecutionCanBeCancelledWhenRetriesAreDisabled : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage) })] +// public async Task DuringGetCapabilities_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) +// { +// // ARRANGE +// var rpcCallHasStarted = new Reference(false); +// var hasPausedOrStoppedPortForwarder = false; +// var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue +// .WithServiceEndpointModifier(point => point.TryAndConnectForALongTime()) +// .WithRetriesDisabled() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithPortForwarder(out var portForwarder) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var capabilitiesMethodUsages) +// .RecordMethodUsages(tentacleConfigurationTestCase, out var scriptMethodUsages) +// .DecorateCapabilitiesServiceV2With(d => d +// .BeforeGetCapabilities( +// async () => +// { +// if (!hasPausedOrStoppedPortForwarder) +// { +// hasPausedOrStoppedPortForwarder = true; +// await tcpConnectionUtilities.RestartTcpConnection(); +// +// PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); +// } +// } +// +// ensureCancellationOccursDuringAnRpcCall.Release(); +// }) +// .AfterGetCapabilities( +// async _ => +// { +// await ensureCancellationOccursDuringAnRpcCall.WaitAsync(CancellationToken); +// })) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("Should not run this script") +// .Sleep(TimeSpan.FromHours(1))) +// .Build(); +// +// // ACT +// var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); +// +// // ASSERT +// // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring +// capabilitiesMethodUsages.ForGetCapabilitiesAsync().LastException.Should().BeRequestCancelledException(rpcCallStage); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// actualException.ShouldMatchExceptionContract(expectedException); +// +// cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(15)); +// +// capabilitiesMethodUsages.ForGetCapabilitiesAsync().Started.Should().Be(1); +// scriptMethodUsages.ForStartScriptAsync().Started.Should().Be(0, "Should not have proceeded past GetCapabilities"); +// scriptMethodUsages.ForCancelScriptAsync().Started.Should().Be(0, "Should not have tried to call CancelScript"); +// } +// +// [Test] +// [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage) })] +// public async Task DuringStartScript_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) +// { +// // ARRANGE +// var rpcCallHasStarted = new Reference(false); +// var restartedPortForwarderForCancel = false; +// var hasPausedOrStoppedPortForwarder = false; +// var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue +// .WithServiceEndpointModifier(point => point.TryAndConnectForALongTime()) +// .WithRetriesDisabled() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithPortForwarder(out var portForwarder) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeStartScript( +// async () => +// { +// if (!hasPausedOrStoppedPortForwarder) +// { +// hasPausedOrStoppedPortForwarder = true; +// await tcpConnectionUtilities.RestartTcpConnection(); +// PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); +// } +// } +// +// ensureCancellationOccursDuringAnRpcCall.Release(); +// }) +// .BeforeCancelScript( +// async () => +// { +// await Task.CompletedTask; +// +// if (!restartedPortForwarderForCancel) +// { +// restartedPortForwarderForCancel = true; +// UnPauseOrRestartPortForwarder(tentacleConfigurationTestCase.TentacleType, rpcCallStage, portForwarder); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("The script") +// .Sleep(TimeSpan.FromHours(1))) +// .Build(); +// +// // ACT +// var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); +// +// // ASSERT +// // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring +// recordedUsages.ForStartScriptAsync().LastException.Should().BeRequestCancelledException(rpcCallStage); +// +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// // Should have cancelled the RPC call and exited immediately +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// +// actualException.ShouldMatchExceptionContract(expectedException); +// +// // We should have cancelled the RPC call quickly and existed +// cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(15)); +// +// recordedUsages.ForStartScriptAsync().Started.Should().Be(1); +// recordedUsages.ForGetStatusAsync().Started.Should().Be(0, "Test should not have not proceeded past StartScript before being Cancelled"); +// recordedUsages.ForCancelScriptAsync().Started.Should().Be(0); +// recordedUsages.ForCompleteScriptAsync().Started.Should().Be(0); +// } +// else // Transferring +// { +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// actualException.ShouldMatchExceptionContract(expectedException); +// +// // Assert the CancelScript and CompleteScript flow happened fairly quickly +// cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); +// +// recordedUsages.ForStartScriptAsync().Started.Should().Be(1); +// recordedUsages.ForGetStatusAsync().Started.Should().Be(0, "Test should not have not proceeded past StartScript before being Cancelled"); +// recordedUsages.ForCancelScriptAsync().Started.Should().BeGreaterOrEqualTo(1); +// recordedUsages.ForCompleteScriptAsync().Started.Should().Be(1); +// } +// } +// +// [Test] +// [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage) })] +// public async Task DuringGetStatus_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) +// { +// // ARRANGE +// var rpcCallHasStarted = new Reference(false); +// var restartedPortForwarderForCancel = false; +// var hasPausedOrStoppedPortForwarder = false; +// var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue +// .WithServiceEndpointModifier(point => point.TryAndConnectForALongTime()) +// .WithRetriesDisabled() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithPortForwarder(out var portForwarder) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeGetStatus( +// async () => +// { +// if (!hasPausedOrStoppedPortForwarder) +// { +// hasPausedOrStoppedPortForwarder = true; +// await tcpConnectionUtilities.RestartTcpConnection(); +// PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); +// } +// } +// +// ensureCancellationOccursDuringAnRpcCall.Release(); +// }) +// .AfterGetStatus( +// async () => +// { +// await ensureCancellationOccursDuringAnRpcCall.WaitAsync(CancellationToken); +// }) +// .BeforeCancelScript( +// async () => +// { +// await Task.CompletedTask; +// +// if (!restartedPortForwarderForCancel) +// { +// restartedPortForwarderForCancel = true; +// UnPauseOrRestartPortForwarder(tentacleConfigurationTestCase.TentacleType, rpcCallStage, portForwarder); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("The script") +// .Sleep(TimeSpan.FromHours(1))) +// .Build(); +// +// // ACT +// var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); +// +// // ASSERT +// // Assert that script execution was cancelled +// actualException.Should().BeScriptExecutionCancelledException(); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// actualException.ShouldMatchExceptionContract(expectedException); +// +// // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring +// recordedUsages.ForGetStatusAsync().LastException.Should().BeRequestCancelledException(rpcCallStage); +// +// // Assert the CancelScript and CompleteScript flow happened fairly quickly +// cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); +// +// // The expected RPC calls were made +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().BeGreaterOrEqualTo(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); +// } +// +// [Test] +// [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage) })] +// public async Task DuringCompleteScript_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) +// { +// // ARRANGE +// var rpcCallHasStarted = new Reference(false); +// var hasPausedOrStoppedPortForwarder = false; +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue +// .WithServiceEndpointModifier(point => point.TryAndConnectForALongTime()) +// .WithRetriesDisabled() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithPortForwarder(out var portForwarder) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeCompleteScript( +// async () => +// { +// if (!hasPausedOrStoppedPortForwarder) +// { +// hasPausedOrStoppedPortForwarder = true; +// await tcpConnectionUtilities.RestartTcpConnection(); +// PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); +// } +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// clientAndTentacle.TentacleClient.OnCancellationAbandonCompleteScriptAfter = TimeSpan.FromSeconds(20); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("The script") +// .Sleep(TimeSpan.FromSeconds(5))) +// .Build(); +// +// // ACT +// var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, new SemaphoreSlim(int.MaxValue, int.MaxValue)); +// +// // ASSERT +// // The actual exception may be null if the script completed before the cancellation was observed +// if (actualException != null) +// { +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// actualException.ShouldMatchExceptionContract(expectedException); +// } +// +// // Halibut Errors were recorded on CompleteScript +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).LastException?.Should().Match(x => x is HalibutClientException || x is OperationCanceledException || x is TaskCanceledException); // Complete Script was cancelled quickly +// cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); +// +// // The expected RPC calls were made +// recordedUsages.ForStartScriptAsync().Started.Should().Be(1); +// recordedUsages.ForGetStatusAsync().Started.Should().BeGreaterThanOrEqualTo(1); +// recordedUsages.ForCancelScriptAsync().Started.Should().Be(0); +// recordedUsages.ForCompleteScriptAsync().Started.Should().BeGreaterOrEqualTo(1); +// } +// +// void PauseOrStopPortForwarder(RpcCallStage rpcCallStage, PortForwarder portForwarder, IResponseMessageTcpKiller responseMessageTcpKiller, Reference rpcCallHasStarted) +// { +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// Logger.Information("Killing the port forwarder so the next RPCs are in the connecting state when being cancelled"); +// //portForwarder.Stop(); +// portForwarder.EnterKillNewAndExistingConnectionsMode(); +// rpcCallHasStarted.Value = true; +// } +// else +// { +// Logger.Information("Will Pause the port forwarder on next response so the next RPC is in-flight when being cancelled"); +// responseMessageTcpKiller.PauseConnectionOnNextResponse(() => rpcCallHasStarted.Value = true); +// } +// } +// +// void UnPauseOrRestartPortForwarder(TentacleType tentacleType, RpcCallStage rpcCallStage, Reference portForwarder) +// { +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// Logger.Information("Starting the PortForwarder as we stopped it to get the StartScript RPC call in the Connecting state"); +// //portForwarder.Value.Start(); +// portForwarder.Value.ReturnToNormalMode(); +// } +// else if (tentacleType == TentacleType.Polling) +// { +// Logger.Information("UnPausing the PortForwarder as we paused the connections which means Polling will be stalled"); +// portForwarder.Value.UnPauseExistingConnections(); +// portForwarder.Value.CloseExistingConnections(); +// } +// } +// +// async Task<(ScriptExecutionResult response, Exception? actualException, TimeSpan cancellationDuration)> ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted( +// ClientAndTentacle clientAndTentacle, +// ExecuteScriptCommand executeScriptCommand, +// Reference rpcCallHasStarted, +// SemaphoreSlim whenTheRequestCanBeCancelled) +// { +// Logger.Information("Start of ExecuteScriptThenCancel"); +// var cancelExecutionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); +// +// var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript( +// executeScriptCommand, +// cancelExecutionCancellationTokenSource.Token); +// +// Logger.Information("Create action"); +// Func)>> action = async () => await executeScriptTask; +// +// Logger.Information("Waiting for the RPC Call to start"); +// await Wait.For( +// () => rpcCallHasStarted.Value, +// TimeSpan.FromSeconds(30), +// () => throw new Exception("RPC call did not start") +// ,CancellationToken); +// Logger.Information("RPC Call has start"); +// +// await Task.Delay(TimeSpan.FromSeconds(6), CancellationToken); +// +// var cancellationDuration = new Stopwatch(); +// await whenTheRequestCanBeCancelled.WithLockAsync(() => +// { +// Logger.Information("Cancelling ExecuteScript"); +// cancelExecutionCancellationTokenSource.Cancel(); +// cancellationDuration.Start(); +// }, CancellationToken); +// +// Exception? actualException = null; +// (ScriptExecutionResult Response, List Logs)? responseAndLogs = null; +// try +// { +// responseAndLogs = await action(); +// } +// catch (Exception ex) +// { +// actualException = ex; +// } +// +// cancellationDuration.Stop(); +// return (responseAndLogs?.Response, actualException, cancellationDuration.Elapsed); +// } +// } +// } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreEnabled.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreEnabled.cs index 693adbd07..4d855bda2 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreEnabled.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreEnabled.cs @@ -1,672 +1,672 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut; -using Halibut.Diagnostics; -using NUnit.Framework; -using Octopus.Tentacle.Client.Scripts; -using Octopus.Tentacle.Client.Scripts.Models; -using Octopus.Tentacle.CommonTestUtils.Builders; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.Capabilities; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators.Proxies; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; -using Octopus.Tentacle.Tests.Integration.Support.PendingRequestQueueFactories; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Util.PendingRequestQueueHelpers; -using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; -using Octopus.Tentacle.Util; -using Octopus.TestPortForwarder; - -namespace Octopus.Tentacle.Tests.Integration -{ - /// - /// These tests make sure that we can cancel the ExecuteScript operation when using Tentacle Client with RPC retries enabled. - /// - [IntegrationTestTimeout] - public class ClientScriptExecutionCanBeCancelledWhenRetriesAreEnabled : IntegrationTest - { - [Test] - [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage), typeof(RpcCall) })] - public async Task DuringGetCapabilities_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage, RpcCall rpcCall) - { - // ARRANGE - var rpcCallHasStarted = new Reference(false); - var hasPausedOrStoppedPortForwarder = false; - var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue - .WithServiceEndpointModifier(point => - { - if (rpcCall == RpcCall.FirstCall) point.TryAndConnectForALongTime(); - }) - .WithRetryDuration(TimeSpan.FromHours(1)) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithPortForwarder(out var portForwarder) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .RecordMethodUsages(tentacleConfigurationTestCase, out var scriptMethodUsages) - .DecorateCapabilitiesServiceV2With(d => d - .BeforeGetCapabilities( - async () => - { - if (rpcCall == RpcCall.RetryingCall && - recordedUsages.ForGetCapabilitiesAsync().LastException == null) - { - await tcpConnectionUtilities.RestartTcpConnection(); - - // Kill the first GetCapabilities call to force the rpc call into retries - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - else if (!hasPausedOrStoppedPortForwarder) - { - hasPausedOrStoppedPortForwarder = true; - await tcpConnectionUtilities.RestartTcpConnection(); - - await PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); - if (rpcCallStage == RpcCallStage.Connecting && tentacleConfigurationTestCase.TentacleType == TentacleType.Polling) - { - await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); - } - } - - ensureCancellationOccursDuringAnRpcCall.Release(); - }) - .AfterGetCapabilities( - async _ => - { - await ensureCancellationOccursDuringAnRpcCall.WaitAsync(CancellationToken); - })) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("Should not run this script") - .Sleep(TimeSpan.FromHours(1))) - .Build(); - - // ACT - var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); - - // ASSERT - // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring - var latestException = recordedUsages.ForGetCapabilitiesAsync().LastException; - if (tentacleConfigurationTestCase.TentacleType == TentacleType.Listening && rpcCallStage == RpcCallStage.Connecting && latestException is HalibutClientException) - { - Assert.Inconclusive("This test is very fragile and often it will often cancel when the client is not in a wait trying to connect but instead gets error responses from the proxy. " + - "This results in a halibut client exception being returned rather than a request cancelled error being returned and is not testing the intended scenario"); - } - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - actualException.ShouldMatchExceptionContract(expectedException); - - latestException.Should().BeRequestCancelledException(rpcCallStage); - cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(10), "The RPC call should have been cancelled quickly"); - - if (rpcCall == RpcCall.FirstCall) - { - recordedUsages.ForGetCapabilitiesAsync().Started.Should().Be(1); - } - else - { - recordedUsages.ForGetCapabilitiesAsync().Started.Should().BeGreaterOrEqualTo(2); - } - scriptMethodUsages.ForStartScriptAsync().Started.Should().Be(0, "Should not have proceeded past GetCapabilities"); - scriptMethodUsages.ForCancelScriptAsync().Started.Should().Be(0, "Should not have tried to call CancelScript"); - scriptMethodUsages.ForCompleteScriptAsync().Started.Should().Be(0, "Should not have tried to call CompleteScript"); - } - - /// - /// This test, and probably others in this test class do not correctly test the Connecting Scenario. The port forwarder is used to kill new and existing connections but this can - /// result in Halibut thinking a Request is transferring, where we want it to be connecting. These tests need to be rewritten to ensure they test the correct scenario. - /// - [Test] - [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCall), typeof(RpcCallStage) })] - public async Task DuringStartScript_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCall rpcCall, RpcCallStage rpcCallStage) - { - var rpcCallHasStarted = new Reference(false); - var restartedPortForwarderForCancel = false; - var hasPausedOrStoppedPortForwarder = false; - var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue - .WithServiceEndpointModifier(point => - { - if (rpcCall == RpcCall.FirstCall) point.TryAndConnectForALongTime(); - }) - .WithRetryDuration(TimeSpan.FromHours(1)) - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithPortForwarder(out var portForwarder) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeStartScript( - async () => - { - if (rpcCall == RpcCall.RetryingCall && recordedUsages.ForStartScriptAsync().LastException is null) - { - await tcpConnectionUtilities.RestartTcpConnection(); - // Kill the first StartScript call to force the rpc call into retries - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - else - { - if (!hasPausedOrStoppedPortForwarder) - { - hasPausedOrStoppedPortForwarder = true; - await tcpConnectionUtilities.RestartTcpConnection(); - await PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); - if (rpcCallStage == RpcCallStage.Connecting) - { - await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); - } - } - } - - ensureCancellationOccursDuringAnRpcCall.Release(); - }) - .AfterStartScript( - async () => - { - await ensureCancellationOccursDuringAnRpcCall.WaitAsync(CancellationToken); - }) - .BeforeCancelScript( - async () => - { - await Task.CompletedTask; - - if (!restartedPortForwarderForCancel) - { - restartedPortForwarderForCancel = true; - UnPauseOrRestartPortForwarder(tentacleConfigurationTestCase.TentacleType, rpcCallStage, portForwarder); - } - })) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("The script") - .Sleep(TimeSpan.FromHours(1))) - .Build(); - - // ACT - var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); - - // ASSERT - // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring - var latestException = recordedUsages.ForStartScriptAsync().LastException; - if (tentacleConfigurationTestCase.TentacleType == TentacleType.Listening && rpcCallStage == RpcCallStage.Connecting && latestException is HalibutClientException) - { - Assert.Inconclusive("This test is very fragile and often it will often cancel when the client is not in a wait trying to connect but instead gets error responses from the proxy. " + - "This results in a halibut client exception being returned rather than a request cancelled error being returned and is not testing the intended scenario"); - } - latestException.Should().BeRequestCancelledException(rpcCallStage); - - if (rpcCall == RpcCall.FirstCall && rpcCallStage == RpcCallStage.Connecting) - { - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - actualException.ShouldMatchExceptionContract(expectedException); - - // We should have cancelled the RPC call quickly - cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(15)); - - recordedUsages.ForStartScriptAsync().Started.Should().Be(1); - recordedUsages.ForGetStatusAsync().Started.Should().Be(0); - recordedUsages.ForCancelScriptAsync().Started.Should().Be(0); - recordedUsages.ForCompleteScriptAsync().Started.Should().Be(0); - } - else if((rpcCall == RpcCall.RetryingCall && rpcCallStage == RpcCallStage.Connecting) || rpcCallStage == RpcCallStage.InFlight) - { - // Assert that script execution was cancelled - actualException.Should().BeScriptExecutionCancelledException(); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - actualException.ShouldMatchExceptionContract(expectedException); - - - // Assert the CancelScript and CompleteScript flow happened fairly quickly - cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); - - if (rpcCall == RpcCall.FirstCall) - { - recordedUsages.ForStartScriptAsync().Started.Should().Be(1); - } - else - { - recordedUsages.ForStartScriptAsync().Started.Should().BeGreaterOrEqualTo(2); - } - recordedUsages.ForGetStatusAsync().Started.Should().Be(0); - recordedUsages.ForCancelScriptAsync().Started.Should().BeGreaterOrEqualTo(1); - recordedUsages.ForCompleteScriptAsync().Started.Should().Be(1); - } - else - { - throw new ArgumentOutOfRangeException(); - } - } - - [Test] - [TentacleConfigurations(testListening: false)] - public async Task DuringStartScript_ForPollingTentacle_ThatIsRetryingTheRpc_AndConnecting_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var halibutTimeoutsAndLimits = HalibutTimeoutsAndLimits.RecommendedValues(); - halibutTimeoutsAndLimits.PollingQueueWaitTimeout = TimeSpan.FromSeconds(4); - halibutTimeoutsAndLimits.PollingRequestQueueTimeout = TimeSpan.FromSeconds(3); - - var started = false; - var cancelExecutionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithRetryDuration(TimeSpan.FromHours(1)) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder().RecordMethodUsages(tentacleConfigurationTestCase, out var scriptServiceRecordedUsages).Build()) - .WithPendingRequestQueueFactory(new CancelWhenRequestQueuedPendingRequestQueueFactory( - cancelExecutionCancellationTokenSource, - halibutTimeoutsAndLimits, - // Cancel the execution when the StartScript RPC call is being retries - shouldCancel: async () => - { - await Task.CompletedTask; - - if (started && scriptServiceRecordedUsages.ForStartScriptAsync().Started >= 2) - { - Logger.Information("Cancelling Execute Script"); - return true; - } - - return false; - })) - .WithHalibutTimeoutsAndLimits(halibutTimeoutsAndLimits) - .Build(CancellationToken); - - // Arrange - Logger.Information("Execute a script so that GetCapabilities will be cached"); - await clientAndTentacle.TentacleClient.ExecuteScript( - new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.Print("The script")).Build(), - cancelExecutionCancellationTokenSource.Token); - - Logger.Information("Stop Tentacle so no more requests are picked up"); - await clientAndTentacle.RunningTentacle.Stop(CancellationToken); - - var delay = clientAndTentacle.Server.ServerHalibutRuntime.TimeoutsAndLimits.PollingQueueWaitTimeout + TimeSpan.FromSeconds(2); - Logger.Information($"Waiting for {delay} for any active PendingRequestQueues for the Tentacle to drop the latest poll connection"); - await Task.Delay(delay, CancellationToken); - - scriptServiceRecordedUsages.Reset(); - started = true; - - // ACT - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b.Print("The script").Sleep(TimeSpan.FromHours(1))) - .Build(); - - Logger.Information("Start Executing the Script"); - var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, cancelExecutionCancellationTokenSource.Token); - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - // Assert - scriptServiceRecordedUsages.ForStartScriptAsync().Completed.Should().BeGreaterOrEqualTo(2); - scriptServiceRecordedUsages.ForGetStatusAsync().Started.Should().Be(0); - scriptServiceRecordedUsages.ForCancelScriptAsync().Started.Should().BeGreaterOrEqualTo(0, "Script Execution does not need to be cancelled on Tentacle as it has not started"); - scriptServiceRecordedUsages.ForCompleteScriptAsync().Started.Should().Be(0); - } - - [Test] - [TentacleConfigurations(testPolling: false)] - public async Task DuringStartScript_ForListeningTentacle_ThatIsRetryingTheRpc_AndConnecting_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var halibutTimeoutsAndLimits = HalibutTimeoutsAndLimits.RecommendedValues(); - halibutTimeoutsAndLimits.RetryCountLimit = 1; - halibutTimeoutsAndLimits.ConnectionErrorRetryTimeout = TimeSpan.FromSeconds(4); - halibutTimeoutsAndLimits.RetryListeningSleepInterval = TimeSpan.Zero; - halibutTimeoutsAndLimits.TcpClientConnectTimeout = TimeSpan.FromSeconds(4); - halibutTimeoutsAndLimits.TcpClientPooledConnectionTimeout = TimeSpan.FromSeconds(5); - - var cancelExecutionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithRetryDuration(TimeSpan.FromHours(1)) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder().RecordMethodUsages(tentacleConfigurationTestCase, out var scriptServiceRecordedUsages).Build()) - .WithHalibutTimeoutsAndLimits(halibutTimeoutsAndLimits) - .WithPortForwarder(out var portForwarder) - .Build(CancellationToken); - - // Arrange - Logger.Information("Execute a script so that GetCapabilities will be cached"); - await clientAndTentacle.TentacleClient.ExecuteScript( - new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.Print("The script")).Build(), - cancelExecutionCancellationTokenSource.Token); - - Logger.Information("Stop Tentacle so no more requests are picked up"); - await clientAndTentacle.RunningTentacle.Stop(CancellationToken); - portForwarder.Value.KillNewConnectionsImmediatlyMode = true; - portForwarder.Value.CloseExistingConnections(); - - var delay = clientAndTentacle.Server.ServerHalibutRuntime.TimeoutsAndLimits.SafeTcpClientPooledConnectionTimeout + TimeSpan.FromSeconds(2); - Logger.Information($"Waiting for {delay} for any active Pooled Connections for the Tentacle to expire"); - await Task.Delay(delay, CancellationToken); - - scriptServiceRecordedUsages.Reset(); - - // ACT - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b.Print("The script").Sleep(TimeSpan.FromHours(1))) - .Build(); - - Logger.Information("Start Executing the Script"); - var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, cancelExecutionCancellationTokenSource.Token); - var cancellationTask = Task.Run(async () => - { - while (true) - { - if (scriptServiceRecordedUsages.ForStartScriptAsync().Started >= 2) - { - cancelExecutionCancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(2)); - Logger.Information("Cancelling cancellation token source after 2 seconds."); - return; - } - - await Task.Delay(TimeSpan.FromSeconds(0.5), CancellationToken); - } - }, CancellationToken); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - await cancellationTask; - - // Assert - scriptServiceRecordedUsages.ForStartScriptAsync().Completed.Should().BeGreaterOrEqualTo(2); - scriptServiceRecordedUsages.ForGetStatusAsync().Started.Should().Be(0); - scriptServiceRecordedUsages.ForCancelScriptAsync().Started.Should().BeGreaterOrEqualTo(0, "Script Execution does not need to be cancelled on Tentacle as it has not started"); - scriptServiceRecordedUsages.ForCompleteScriptAsync().Started.Should().Be(0); - } - - [Test] - [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCall), typeof(RpcCallStage) })] - public async Task DuringGetStatus_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCall rpcCall, RpcCallStage rpcCallStage) - { - // ARRANGE - var rpcCallHasStarted = new Reference(false); - var restartedPortForwarderForCancel = false; - var hasPausedOrStoppedPortForwarder = false; - var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue - .WithServiceEndpointModifier(point => - { - if (rpcCall == RpcCall.FirstCall) point.TryAndConnectForALongTime(); - }) - .WithRetryDuration(TimeSpan.FromHours(1)) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithPortForwarder(out var portForwarder) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeGetStatus( - async () => - { - if (rpcCall == RpcCall.RetryingCall && - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException is null) - { - await tcpConnectionUtilities.RestartTcpConnection(); - // Kill the first StartScript call to force the rpc call into retries - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - else - { - if (!hasPausedOrStoppedPortForwarder) - { - hasPausedOrStoppedPortForwarder = true; - await tcpConnectionUtilities.RestartTcpConnection(); - await PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); - if (rpcCallStage == RpcCallStage.Connecting) - { - await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); - } - } - } - - ensureCancellationOccursDuringAnRpcCall.Release(); - }) - .AfterGetStatus( - async () => - { - await ensureCancellationOccursDuringAnRpcCall.WaitAsync(CancellationToken); - }) - .BeforeCancelScript( - async () => - { - await Task.CompletedTask; - - if (!restartedPortForwarderForCancel) - { - restartedPortForwarderForCancel = true; - UnPauseOrRestartPortForwarder(tentacleConfigurationTestCase.TentacleType, rpcCallStage, portForwarder); - } - })) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("The script") - .Sleep(TimeSpan.FromHours(1))) - .Build(); - - // ACT - var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); - - // ASSERT - // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring - var latestException = recordedUsages.ForGetStatusAsync().LastException; - if (tentacleConfigurationTestCase.TentacleType == TentacleType.Listening && rpcCallStage == RpcCallStage.Connecting && latestException is HalibutClientException) - { - Assert.Inconclusive("This test is very fragile and often it will often cancel when the client is not in a wait trying to connect but instead gets error responses from the proxy. " + - "This results in a halibut client exception being returned rather than a request cancelled error being returned and is not testing the intended scenario"); - } - - latestException.Should().BeRequestCancelledException(rpcCallStage); - - // Assert that script execution was cancelled - actualException.Should().BeScriptExecutionCancelledException(); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - actualException.ShouldMatchExceptionContract(expectedException); - - - // Script Execution should cancel quickly - cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); - - recordedUsages.ForStartScriptAsync().Started.Should().Be(1); - if (rpcCall == RpcCall.FirstCall) - { - recordedUsages.ForGetStatusAsync().Started.Should().Be(1); - } - else - { - recordedUsages.ForGetStatusAsync().Started.Should().BeGreaterOrEqualTo(2); - } - recordedUsages.ForCancelScriptAsync().Started.Should().BeGreaterOrEqualTo(1); - recordedUsages.ForCompleteScriptAsync().Started.Should().Be(1); - } - - [Test] - [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage) })] - public async Task DuringCompleteScript_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) - { - // ARRANGE - var rpcCallHasStarted = new Reference(false); - var hasPausedOrStoppedPortForwarder = false; - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue - .WithRetryDuration(TimeSpan.FromHours(1)) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithPortForwarder(out var portForwarder) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeCompleteScript( - async () => - { - if (!hasPausedOrStoppedPortForwarder) - { - hasPausedOrStoppedPortForwarder = true; - await tcpConnectionUtilities.RestartTcpConnection(); - await PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); - if (rpcCallStage == RpcCallStage.Connecting) - { - await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); - } - } - })) - .Build()) - .Build(CancellationToken); - - clientAndTentacle.TentacleClient.OnCancellationAbandonCompleteScriptAfter = TimeSpan.FromSeconds(20); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("The script") - .Sleep(TimeSpan.FromSeconds(5))) - .Build(); - - // ACT - var (responseAndLogs, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, new SemaphoreSlim(int.MaxValue, int.MaxValue)); - - // ASSERT - - // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring - var latestException = recordedUsages.ForCompleteScriptAsync().LastException; - if (tentacleConfigurationTestCase.TentacleType == TentacleType.Listening && rpcCallStage == RpcCallStage.Connecting && latestException is HalibutClientException) - { - Assert.Inconclusive("This test is very fragile and often it will often cancel when the client is not in a wait trying to connect but instead gets error responses from the proxy. " + - "This results in a halibut client exception being returned rather than a request cancelled error being returned and is not testing the intended scenario"); - } - - // The actual exception may be null if the script completed before the cancellation was observed - if (actualException != null) - { - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - actualException.ShouldMatchExceptionContract(expectedException); - } - - latestException.Should().BeRequestCancelledException(rpcCallStage); - - // Complete Script was cancelled quickly - cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); - - recordedUsages.ForStartScriptAsync().Started.Should().Be(1); - recordedUsages.ForGetStatusAsync().Started.Should().BeGreaterThanOrEqualTo(1); - recordedUsages.ForCancelScriptAsync().Started.Should().Be(0); - recordedUsages.ForCompleteScriptAsync().Started.Should().BeGreaterOrEqualTo(1); - } - - async Task PauseOrStopPortForwarder(RpcCallStage rpcCallStage, PortForwarder portForwarder, IResponseMessageTcpKiller responseMessageTcpKiller, Reference rpcCallHasStarted) - { - if (rpcCallStage == RpcCallStage.Connecting) - { - Logger.Information("Killing the port forwarder so the next RPCs are in the connecting state when being cancelled"); - portForwarder.EnterKillNewAndExistingConnectionsMode(); - - await SetRpcCallWeAreInterestedInAsStarted(rpcCallHasStarted); - } - else - { - Logger.Information("Will Pause the port forwarder on next response so the next RPC is in-flight when being cancelled"); -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - responseMessageTcpKiller.PauseConnectionOnNextResponse(() => SetRpcCallWeAreInterestedInAsStarted(rpcCallHasStarted)); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - } - - static async Task SetRpcCallWeAreInterestedInAsStarted(Reference rpcCallHasStarted) - { - // Allow the port forwarder some time to stop - await Task.Delay(TimeSpan.FromSeconds(5)); - rpcCallHasStarted.Value = true; - } - } - - void UnPauseOrRestartPortForwarder(TentacleType tentacleType, RpcCallStage rpcCallStage, Reference portForwarder) - { - if (rpcCallStage == RpcCallStage.Connecting) - { - Logger.Information("Starting the PortForwarder as we stopped it to get the StartScript RPC call in the Connecting state"); - portForwarder.Value.ReturnToNormalMode(); - } - else if (tentacleType == TentacleType.Polling) - { - Logger.Information("UnPausing the PortForwarder as we paused the connections which means Polling will be stalled"); - portForwarder.Value.UnPauseExistingConnections(); - portForwarder.Value.CloseExistingConnections(); - } - } - - async Task<(ScriptExecutionResult response, Exception? actualException, TimeSpan cancellationDuration)> ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted( - ClientAndTentacle clientAndTentacle, - ExecuteScriptCommand executeScriptCommand, - Reference rpcCallHasStarted, - SemaphoreSlim whenTheRequestCanBeCancelled) - { - var cancelExecutionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); - - var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript( - executeScriptCommand, - cancelExecutionCancellationTokenSource.Token); - - Logger.Information("Waiting for the RPC Call to start"); - await Wait.For(() => rpcCallHasStarted.Value, - TimeSpan.FromSeconds(30), - () => throw new Exception("RPC call did not start"), - CancellationToken); - Logger.Information("RPC Call has start"); - - var cancellationDuration = new Stopwatch(); - await whenTheRequestCanBeCancelled.WithLockAsync(async () => - { - await Task.Delay(TimeSpan.FromSeconds(6), CancellationToken); - Logger.Information("Cancelling ExecuteScript"); - cancelExecutionCancellationTokenSource.Cancel(); - cancellationDuration.Start(); - }, CancellationToken); - - Exception? actualException = null; - (ScriptExecutionResult Response, List Logs)? responseAndLogs = null; - try - { - responseAndLogs = await executeScriptTask; - } - catch (Exception ex) - { - actualException = ex; - } - - cancellationDuration.Stop(); - return (responseAndLogs?.Response, actualException, cancellationDuration.Elapsed); - } - } -} \ No newline at end of file +// using System; +// using System.Collections.Generic; +// using System.Diagnostics; +// using System.Threading; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut; +// using Halibut.Diagnostics; +// using NUnit.Framework; +// using Octopus.Tentacle.Client.Scripts; +// using Octopus.Tentacle.Client.Scripts.Models; +// using Octopus.Tentacle.CommonTestUtils.Builders; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Contracts.Capabilities; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators.Proxies; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; +// using Octopus.Tentacle.Tests.Integration.Support.PendingRequestQueueFactories; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Util.PendingRequestQueueHelpers; +// using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; +// using Octopus.Tentacle.Util; +// using Octopus.TestPortForwarder; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// /// +// /// These tests make sure that we can cancel the ExecuteScript operation when using Tentacle Client with RPC retries enabled. +// /// +// [IntegrationTestTimeout] +// public class ClientScriptExecutionCanBeCancelledWhenRetriesAreEnabled : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage), typeof(RpcCall) })] +// public async Task DuringGetCapabilities_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage, RpcCall rpcCall) +// { +// // ARRANGE +// var rpcCallHasStarted = new Reference(false); +// var hasPausedOrStoppedPortForwarder = false; +// var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue +// .WithServiceEndpointModifier(point => +// { +// if (rpcCall == RpcCall.FirstCall) point.TryAndConnectForALongTime(); +// }) +// .WithRetryDuration(TimeSpan.FromHours(1)) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithPortForwarder(out var portForwarder) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .RecordMethodUsages(tentacleConfigurationTestCase, out var scriptMethodUsages) +// .DecorateCapabilitiesServiceV2With(d => d +// .BeforeGetCapabilities( +// async () => +// { +// if (rpcCall == RpcCall.RetryingCall && +// recordedUsages.ForGetCapabilitiesAsync().LastException == null) +// { +// await tcpConnectionUtilities.RestartTcpConnection(); +// +// // Kill the first GetCapabilities call to force the rpc call into retries +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// else if (!hasPausedOrStoppedPortForwarder) +// { +// hasPausedOrStoppedPortForwarder = true; +// await tcpConnectionUtilities.RestartTcpConnection(); +// +// await PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); +// if (rpcCallStage == RpcCallStage.Connecting && tentacleConfigurationTestCase.TentacleType == TentacleType.Polling) +// { +// await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); +// } +// } +// +// ensureCancellationOccursDuringAnRpcCall.Release(); +// }) +// .AfterGetCapabilities( +// async _ => +// { +// await ensureCancellationOccursDuringAnRpcCall.WaitAsync(CancellationToken); +// })) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("Should not run this script") +// .Sleep(TimeSpan.FromHours(1))) +// .Build(); +// +// // ACT +// var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); +// +// // ASSERT +// // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring +// var latestException = recordedUsages.ForGetCapabilitiesAsync().LastException; +// if (tentacleConfigurationTestCase.TentacleType == TentacleType.Listening && rpcCallStage == RpcCallStage.Connecting && latestException is HalibutClientException) +// { +// Assert.Inconclusive("This test is very fragile and often it will often cancel when the client is not in a wait trying to connect but instead gets error responses from the proxy. " + +// "This results in a halibut client exception being returned rather than a request cancelled error being returned and is not testing the intended scenario"); +// } +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// actualException.ShouldMatchExceptionContract(expectedException); +// +// latestException.Should().BeRequestCancelledException(rpcCallStage); +// cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(10), "The RPC call should have been cancelled quickly"); +// +// if (rpcCall == RpcCall.FirstCall) +// { +// recordedUsages.ForGetCapabilitiesAsync().Started.Should().Be(1); +// } +// else +// { +// recordedUsages.ForGetCapabilitiesAsync().Started.Should().BeGreaterOrEqualTo(2); +// } +// scriptMethodUsages.ForStartScriptAsync().Started.Should().Be(0, "Should not have proceeded past GetCapabilities"); +// scriptMethodUsages.ForCancelScriptAsync().Started.Should().Be(0, "Should not have tried to call CancelScript"); +// scriptMethodUsages.ForCompleteScriptAsync().Started.Should().Be(0, "Should not have tried to call CompleteScript"); +// } +// +// /// +// /// This test, and probably others in this test class do not correctly test the Connecting Scenario. The port forwarder is used to kill new and existing connections but this can +// /// result in Halibut thinking a Request is transferring, where we want it to be connecting. These tests need to be rewritten to ensure they test the correct scenario. +// /// +// [Test] +// [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCall), typeof(RpcCallStage) })] +// public async Task DuringStartScript_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCall rpcCall, RpcCallStage rpcCallStage) +// { +// var rpcCallHasStarted = new Reference(false); +// var restartedPortForwarderForCancel = false; +// var hasPausedOrStoppedPortForwarder = false; +// var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue +// .WithServiceEndpointModifier(point => +// { +// if (rpcCall == RpcCall.FirstCall) point.TryAndConnectForALongTime(); +// }) +// .WithRetryDuration(TimeSpan.FromHours(1)) +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithPortForwarder(out var portForwarder) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeStartScript( +// async () => +// { +// if (rpcCall == RpcCall.RetryingCall && recordedUsages.ForStartScriptAsync().LastException is null) +// { +// await tcpConnectionUtilities.RestartTcpConnection(); +// // Kill the first StartScript call to force the rpc call into retries +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// else +// { +// if (!hasPausedOrStoppedPortForwarder) +// { +// hasPausedOrStoppedPortForwarder = true; +// await tcpConnectionUtilities.RestartTcpConnection(); +// await PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); +// } +// } +// } +// +// ensureCancellationOccursDuringAnRpcCall.Release(); +// }) +// .AfterStartScript( +// async () => +// { +// await ensureCancellationOccursDuringAnRpcCall.WaitAsync(CancellationToken); +// }) +// .BeforeCancelScript( +// async () => +// { +// await Task.CompletedTask; +// +// if (!restartedPortForwarderForCancel) +// { +// restartedPortForwarderForCancel = true; +// UnPauseOrRestartPortForwarder(tentacleConfigurationTestCase.TentacleType, rpcCallStage, portForwarder); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("The script") +// .Sleep(TimeSpan.FromHours(1))) +// .Build(); +// +// // ACT +// var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); +// +// // ASSERT +// // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring +// var latestException = recordedUsages.ForStartScriptAsync().LastException; +// if (tentacleConfigurationTestCase.TentacleType == TentacleType.Listening && rpcCallStage == RpcCallStage.Connecting && latestException is HalibutClientException) +// { +// Assert.Inconclusive("This test is very fragile and often it will often cancel when the client is not in a wait trying to connect but instead gets error responses from the proxy. " + +// "This results in a halibut client exception being returned rather than a request cancelled error being returned and is not testing the intended scenario"); +// } +// latestException.Should().BeRequestCancelledException(rpcCallStage); +// +// if (rpcCall == RpcCall.FirstCall && rpcCallStage == RpcCallStage.Connecting) +// { +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// actualException.ShouldMatchExceptionContract(expectedException); +// +// // We should have cancelled the RPC call quickly +// cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(15)); +// +// recordedUsages.ForStartScriptAsync().Started.Should().Be(1); +// recordedUsages.ForGetStatusAsync().Started.Should().Be(0); +// recordedUsages.ForCancelScriptAsync().Started.Should().Be(0); +// recordedUsages.ForCompleteScriptAsync().Started.Should().Be(0); +// } +// else if((rpcCall == RpcCall.RetryingCall && rpcCallStage == RpcCallStage.Connecting) || rpcCallStage == RpcCallStage.InFlight) +// { +// // Assert that script execution was cancelled +// actualException.Should().BeScriptExecutionCancelledException(); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// actualException.ShouldMatchExceptionContract(expectedException); +// +// +// // Assert the CancelScript and CompleteScript flow happened fairly quickly +// cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); +// +// if (rpcCall == RpcCall.FirstCall) +// { +// recordedUsages.ForStartScriptAsync().Started.Should().Be(1); +// } +// else +// { +// recordedUsages.ForStartScriptAsync().Started.Should().BeGreaterOrEqualTo(2); +// } +// recordedUsages.ForGetStatusAsync().Started.Should().Be(0); +// recordedUsages.ForCancelScriptAsync().Started.Should().BeGreaterOrEqualTo(1); +// recordedUsages.ForCompleteScriptAsync().Started.Should().Be(1); +// } +// else +// { +// throw new ArgumentOutOfRangeException(); +// } +// } +// +// [Test] +// [TentacleConfigurations(testListening: false)] +// public async Task DuringStartScript_ForPollingTentacle_ThatIsRetryingTheRpc_AndConnecting_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var halibutTimeoutsAndLimits = HalibutTimeoutsAndLimits.RecommendedValues(); +// halibutTimeoutsAndLimits.PollingQueueWaitTimeout = TimeSpan.FromSeconds(4); +// halibutTimeoutsAndLimits.PollingRequestQueueTimeout = TimeSpan.FromSeconds(3); +// +// var started = false; +// var cancelExecutionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithRetryDuration(TimeSpan.FromHours(1)) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder().RecordMethodUsages(tentacleConfigurationTestCase, out var scriptServiceRecordedUsages).Build()) +// .WithPendingRequestQueueFactory(new CancelWhenRequestQueuedPendingRequestQueueFactory( +// cancelExecutionCancellationTokenSource, +// halibutTimeoutsAndLimits, +// // Cancel the execution when the StartScript RPC call is being retries +// shouldCancel: async () => +// { +// await Task.CompletedTask; +// +// if (started && scriptServiceRecordedUsages.ForStartScriptAsync().Started >= 2) +// { +// Logger.Information("Cancelling Execute Script"); +// return true; +// } +// +// return false; +// })) +// .WithHalibutTimeoutsAndLimits(halibutTimeoutsAndLimits) +// .Build(CancellationToken); +// +// // Arrange +// Logger.Information("Execute a script so that GetCapabilities will be cached"); +// await clientAndTentacle.TentacleClient.ExecuteScript( +// new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.Print("The script")).Build(), +// cancelExecutionCancellationTokenSource.Token); +// +// Logger.Information("Stop Tentacle so no more requests are picked up"); +// await clientAndTentacle.RunningTentacle.Stop(CancellationToken); +// +// var delay = clientAndTentacle.Server.ServerHalibutRuntime.TimeoutsAndLimits.PollingQueueWaitTimeout + TimeSpan.FromSeconds(2); +// Logger.Information($"Waiting for {delay} for any active PendingRequestQueues for the Tentacle to drop the latest poll connection"); +// await Task.Delay(delay, CancellationToken); +// +// scriptServiceRecordedUsages.Reset(); +// started = true; +// +// // ACT +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b.Print("The script").Sleep(TimeSpan.FromHours(1))) +// .Build(); +// +// Logger.Information("Start Executing the Script"); +// var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, cancelExecutionCancellationTokenSource.Token); +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// // Assert +// scriptServiceRecordedUsages.ForStartScriptAsync().Completed.Should().BeGreaterOrEqualTo(2); +// scriptServiceRecordedUsages.ForGetStatusAsync().Started.Should().Be(0); +// scriptServiceRecordedUsages.ForCancelScriptAsync().Started.Should().BeGreaterOrEqualTo(0, "Script Execution does not need to be cancelled on Tentacle as it has not started"); +// scriptServiceRecordedUsages.ForCompleteScriptAsync().Started.Should().Be(0); +// } +// +// [Test] +// [TentacleConfigurations(testPolling: false)] +// public async Task DuringStartScript_ForListeningTentacle_ThatIsRetryingTheRpc_AndConnecting_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var halibutTimeoutsAndLimits = HalibutTimeoutsAndLimits.RecommendedValues(); +// halibutTimeoutsAndLimits.RetryCountLimit = 1; +// halibutTimeoutsAndLimits.ConnectionErrorRetryTimeout = TimeSpan.FromSeconds(4); +// halibutTimeoutsAndLimits.RetryListeningSleepInterval = TimeSpan.Zero; +// halibutTimeoutsAndLimits.TcpClientConnectTimeout = TimeSpan.FromSeconds(4); +// halibutTimeoutsAndLimits.TcpClientPooledConnectionTimeout = TimeSpan.FromSeconds(5); +// +// var cancelExecutionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithRetryDuration(TimeSpan.FromHours(1)) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder().RecordMethodUsages(tentacleConfigurationTestCase, out var scriptServiceRecordedUsages).Build()) +// .WithHalibutTimeoutsAndLimits(halibutTimeoutsAndLimits) +// .WithPortForwarder(out var portForwarder) +// .Build(CancellationToken); +// +// // Arrange +// Logger.Information("Execute a script so that GetCapabilities will be cached"); +// await clientAndTentacle.TentacleClient.ExecuteScript( +// new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.Print("The script")).Build(), +// cancelExecutionCancellationTokenSource.Token); +// +// Logger.Information("Stop Tentacle so no more requests are picked up"); +// await clientAndTentacle.RunningTentacle.Stop(CancellationToken); +// portForwarder.Value.KillNewConnectionsImmediatlyMode = true; +// portForwarder.Value.CloseExistingConnections(); +// +// var delay = clientAndTentacle.Server.ServerHalibutRuntime.TimeoutsAndLimits.SafeTcpClientPooledConnectionTimeout + TimeSpan.FromSeconds(2); +// Logger.Information($"Waiting for {delay} for any active Pooled Connections for the Tentacle to expire"); +// await Task.Delay(delay, CancellationToken); +// +// scriptServiceRecordedUsages.Reset(); +// +// // ACT +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b.Print("The script").Sleep(TimeSpan.FromHours(1))) +// .Build(); +// +// Logger.Information("Start Executing the Script"); +// var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, cancelExecutionCancellationTokenSource.Token); +// var cancellationTask = Task.Run(async () => +// { +// while (true) +// { +// if (scriptServiceRecordedUsages.ForStartScriptAsync().Started >= 2) +// { +// cancelExecutionCancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(2)); +// Logger.Information("Cancelling cancellation token source after 2 seconds."); +// return; +// } +// +// await Task.Delay(TimeSpan.FromSeconds(0.5), CancellationToken); +// } +// }, CancellationToken); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// await cancellationTask; +// +// // Assert +// scriptServiceRecordedUsages.ForStartScriptAsync().Completed.Should().BeGreaterOrEqualTo(2); +// scriptServiceRecordedUsages.ForGetStatusAsync().Started.Should().Be(0); +// scriptServiceRecordedUsages.ForCancelScriptAsync().Started.Should().BeGreaterOrEqualTo(0, "Script Execution does not need to be cancelled on Tentacle as it has not started"); +// scriptServiceRecordedUsages.ForCompleteScriptAsync().Started.Should().Be(0); +// } +// +// [Test] +// [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCall), typeof(RpcCallStage) })] +// public async Task DuringGetStatus_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCall rpcCall, RpcCallStage rpcCallStage) +// { +// // ARRANGE +// var rpcCallHasStarted = new Reference(false); +// var restartedPortForwarderForCancel = false; +// var hasPausedOrStoppedPortForwarder = false; +// var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue +// .WithServiceEndpointModifier(point => +// { +// if (rpcCall == RpcCall.FirstCall) point.TryAndConnectForALongTime(); +// }) +// .WithRetryDuration(TimeSpan.FromHours(1)) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithPortForwarder(out var portForwarder) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeGetStatus( +// async () => +// { +// if (rpcCall == RpcCall.RetryingCall && +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException is null) +// { +// await tcpConnectionUtilities.RestartTcpConnection(); +// // Kill the first StartScript call to force the rpc call into retries +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// else +// { +// if (!hasPausedOrStoppedPortForwarder) +// { +// hasPausedOrStoppedPortForwarder = true; +// await tcpConnectionUtilities.RestartTcpConnection(); +// await PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); +// } +// } +// } +// +// ensureCancellationOccursDuringAnRpcCall.Release(); +// }) +// .AfterGetStatus( +// async () => +// { +// await ensureCancellationOccursDuringAnRpcCall.WaitAsync(CancellationToken); +// }) +// .BeforeCancelScript( +// async () => +// { +// await Task.CompletedTask; +// +// if (!restartedPortForwarderForCancel) +// { +// restartedPortForwarderForCancel = true; +// UnPauseOrRestartPortForwarder(tentacleConfigurationTestCase.TentacleType, rpcCallStage, portForwarder); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("The script") +// .Sleep(TimeSpan.FromHours(1))) +// .Build(); +// +// // ACT +// var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); +// +// // ASSERT +// // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring +// var latestException = recordedUsages.ForGetStatusAsync().LastException; +// if (tentacleConfigurationTestCase.TentacleType == TentacleType.Listening && rpcCallStage == RpcCallStage.Connecting && latestException is HalibutClientException) +// { +// Assert.Inconclusive("This test is very fragile and often it will often cancel when the client is not in a wait trying to connect but instead gets error responses from the proxy. " + +// "This results in a halibut client exception being returned rather than a request cancelled error being returned and is not testing the intended scenario"); +// } +// +// latestException.Should().BeRequestCancelledException(rpcCallStage); +// +// // Assert that script execution was cancelled +// actualException.Should().BeScriptExecutionCancelledException(); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// actualException.ShouldMatchExceptionContract(expectedException); +// +// +// // Script Execution should cancel quickly +// cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); +// +// recordedUsages.ForStartScriptAsync().Started.Should().Be(1); +// if (rpcCall == RpcCall.FirstCall) +// { +// recordedUsages.ForGetStatusAsync().Started.Should().Be(1); +// } +// else +// { +// recordedUsages.ForGetStatusAsync().Started.Should().BeGreaterOrEqualTo(2); +// } +// recordedUsages.ForCancelScriptAsync().Started.Should().BeGreaterOrEqualTo(1); +// recordedUsages.ForCompleteScriptAsync().Started.Should().Be(1); +// } +// +// [Test] +// [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage) })] +// public async Task DuringCompleteScript_ScriptExecutionCanBeCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) +// { +// // ARRANGE +// var rpcCallHasStarted = new Reference(false); +// var hasPausedOrStoppedPortForwarder = false; +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue +// .WithRetryDuration(TimeSpan.FromHours(1)) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithPortForwarder(out var portForwarder) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeCompleteScript( +// async () => +// { +// if (!hasPausedOrStoppedPortForwarder) +// { +// hasPausedOrStoppedPortForwarder = true; +// await tcpConnectionUtilities.RestartTcpConnection(); +// await PauseOrStopPortForwarder(rpcCallStage, portForwarder.Value, responseMessageTcpKiller, rpcCallHasStarted); +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// await tcpConnectionUtilities.EnsurePollingQueueWontSendMessageToDisconnectedTentacles(); +// } +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// clientAndTentacle.TentacleClient.OnCancellationAbandonCompleteScriptAfter = TimeSpan.FromSeconds(20); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("The script") +// .Sleep(TimeSpan.FromSeconds(5))) +// .Build(); +// +// // ACT +// var (responseAndLogs, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, new SemaphoreSlim(int.MaxValue, int.MaxValue)); +// +// // ASSERT +// +// // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring +// var latestException = recordedUsages.ForCompleteScriptAsync().LastException; +// if (tentacleConfigurationTestCase.TentacleType == TentacleType.Listening && rpcCallStage == RpcCallStage.Connecting && latestException is HalibutClientException) +// { +// Assert.Inconclusive("This test is very fragile and often it will often cancel when the client is not in a wait trying to connect but instead gets error responses from the proxy. " + +// "This results in a halibut client exception being returned rather than a request cancelled error being returned and is not testing the intended scenario"); +// } +// +// // The actual exception may be null if the script completed before the cancellation was observed +// if (actualException != null) +// { +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// actualException.ShouldMatchExceptionContract(expectedException); +// } +// +// latestException.Should().BeRequestCancelledException(rpcCallStage); +// +// // Complete Script was cancelled quickly +// cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); +// +// recordedUsages.ForStartScriptAsync().Started.Should().Be(1); +// recordedUsages.ForGetStatusAsync().Started.Should().BeGreaterThanOrEqualTo(1); +// recordedUsages.ForCancelScriptAsync().Started.Should().Be(0); +// recordedUsages.ForCompleteScriptAsync().Started.Should().BeGreaterOrEqualTo(1); +// } +// +// async Task PauseOrStopPortForwarder(RpcCallStage rpcCallStage, PortForwarder portForwarder, IResponseMessageTcpKiller responseMessageTcpKiller, Reference rpcCallHasStarted) +// { +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// Logger.Information("Killing the port forwarder so the next RPCs are in the connecting state when being cancelled"); +// portForwarder.EnterKillNewAndExistingConnectionsMode(); +// +// await SetRpcCallWeAreInterestedInAsStarted(rpcCallHasStarted); +// } +// else +// { +// Logger.Information("Will Pause the port forwarder on next response so the next RPC is in-flight when being cancelled"); +// #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed +// responseMessageTcpKiller.PauseConnectionOnNextResponse(() => SetRpcCallWeAreInterestedInAsStarted(rpcCallHasStarted)); +// #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed +// } +// +// static async Task SetRpcCallWeAreInterestedInAsStarted(Reference rpcCallHasStarted) +// { +// // Allow the port forwarder some time to stop +// await Task.Delay(TimeSpan.FromSeconds(5)); +// rpcCallHasStarted.Value = true; +// } +// } +// +// void UnPauseOrRestartPortForwarder(TentacleType tentacleType, RpcCallStage rpcCallStage, Reference portForwarder) +// { +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// Logger.Information("Starting the PortForwarder as we stopped it to get the StartScript RPC call in the Connecting state"); +// portForwarder.Value.ReturnToNormalMode(); +// } +// else if (tentacleType == TentacleType.Polling) +// { +// Logger.Information("UnPausing the PortForwarder as we paused the connections which means Polling will be stalled"); +// portForwarder.Value.UnPauseExistingConnections(); +// portForwarder.Value.CloseExistingConnections(); +// } +// } +// +// async Task<(ScriptExecutionResult response, Exception? actualException, TimeSpan cancellationDuration)> ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted( +// ClientAndTentacle clientAndTentacle, +// ExecuteScriptCommand executeScriptCommand, +// Reference rpcCallHasStarted, +// SemaphoreSlim whenTheRequestCanBeCancelled) +// { +// var cancelExecutionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); +// +// var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript( +// executeScriptCommand, +// cancelExecutionCancellationTokenSource.Token); +// +// Logger.Information("Waiting for the RPC Call to start"); +// await Wait.For(() => rpcCallHasStarted.Value, +// TimeSpan.FromSeconds(30), +// () => throw new Exception("RPC call did not start"), +// CancellationToken); +// Logger.Information("RPC Call has start"); +// +// var cancellationDuration = new Stopwatch(); +// await whenTheRequestCanBeCancelled.WithLockAsync(async () => +// { +// await Task.Delay(TimeSpan.FromSeconds(6), CancellationToken); +// Logger.Information("Cancelling ExecuteScript"); +// cancelExecutionCancellationTokenSource.Cancel(); +// cancellationDuration.Start(); +// }, CancellationToken); +// +// Exception? actualException = null; +// (ScriptExecutionResult Response, List Logs)? responseAndLogs = null; +// try +// { +// responseAndLogs = await executeScriptTask; +// } +// catch (Exception ex) +// { +// actualException = ex; +// } +// +// cancellationDuration.Stop(); +// return (responseAndLogs?.Response, actualException, cancellationDuration.Elapsed); +// } +// } +// } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanRecoverFromNetworkIssues.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanRecoverFromNetworkIssues.cs index 4f0afb1b6..7511c8a45 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanRecoverFromNetworkIssues.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanRecoverFromNetworkIssues.cs @@ -1,308 +1,308 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.Client; -using Octopus.Tentacle.CommonTestUtils; -using Octopus.Tentacle.CommonTestUtils.Diagnostics; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; -using Octopus.TestPortForwarder; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ClientScriptExecutionCanRecoverFromNetworkIssues : IntegrationTest - { - [Test] - [TentacleConfigurations] - public async Task WhenANetworkFailureOccurs_DuringStartScript_TheClientIsAbleToSuccessfullyCompleteTheScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPortForwarder() - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .Build()) - .Build(CancellationToken); - - var scriptHasStartFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "scripthasstarted"); - var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .CreateFile(scriptHasStartFile) - .WaitForFileToExist(waitForFile) - .Print("hello")) - // Configure the start script command to wait a long time, so we have plenty of time to kill the connection. - .WithDurationStartScriptCanWaitForScriptToFinish(TimeSpan.FromHours(1)) - .Build(); - - var inMemoryLog = new InMemoryLog(); - - var execScriptTask = Task.Run( - async () => await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog), - CancellationToken); - - // Wait for the script to start. - await Wait.For(() => File.Exists(scriptHasStartFile), - TimeSpan.FromSeconds(30), - () => throw new Exception("Script did not start"), - CancellationToken); - - // Now it has started, kill active connections killing the start script request. - clientTentacle.PortForwarder.CloseExistingConnections(); - - // Let the script finish. - File.WriteAllText(waitForFile, ""); - - var (finalResponse, logs) = await execScriptTask; - - finalResponse.State.Should().Be(ProcessState.Complete); - finalResponse.ExitCode.Should().Be(0); - - var allLogs = logs.JoinLogs(); - allLogs.Should().Contain("hello"); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().BeGreaterThan(1); - - inMemoryLog.ShouldHaveLoggedRetryAttemptsAndNoRetryFailures(); - } - - [Test] - [TentacleConfigurations] - public async Task WhenANetworkFailureOccurs_DuringGetStatus_TheClientIsAbleToSuccessfullyCompleteTheScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeGetStatus( - async () => - { - await Task.CompletedTask; - - if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException is null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - })) - .Build()) - .Build(CancellationToken); - - var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("hello") - .WaitForFileToExist(waitForFile) - .Print("AllDone")) - .Build(); - - var inMemoryLog = new InMemoryLog(); - - var execScriptTask = Task.Run( - async () => await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog), - CancellationToken); - - await Wait.For(() => recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException != null, - TimeSpan.FromSeconds(60), - () => throw new Exception("GetStatus did not error"), - CancellationToken); - - // Let the script finish. - File.WriteAllText(waitForFile, ""); - - var (finalResponse, logs) = await execScriptTask; - - finalResponse.State.Should().Be(ProcessState.Complete); - finalResponse.ExitCode.Should().Be(0); - - var allLogs = logs.JoinLogs(); - allLogs.Should().Contain("hello"); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException.Should().NotBeNull(); - - inMemoryLog.ShouldHaveLoggedRetryAttemptsAndNoRetryFailures(); - } - - [Test] - [TentacleConfigurations] - public async Task WhenANetworkFailureOccurs_DuringCompleteScript_TheClientIsAbleToSuccessfullyCompleteTheScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - bool completeScriptWasCalled = false; - PortForwarder? portForwarder = null; - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPortForwarderDataLogging() - .WithServiceEndpointModifier(serviceEndpoint => - { - serviceEndpoint.PollingRequestQueueTimeout = TimeSpan.FromSeconds(10); - serviceEndpoint.RetryCountLimit = 1; - }) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .DecorateAllScriptServicesWith(u => u - .BeforeCompleteScript( - async () => - { - - await Task.CompletedTask; - - completeScriptWasCalled = true; - // A successfully CompleteScript call is not required for the script to be completed. - // So it should be the case that the tentacle can be no longer contactable at this point, - // yet the script execution is marked as successful. - portForwarder.Dispose(); - })) - .Build()) - .Build(CancellationToken); - portForwarder = clientTentacle.PortForwarder; - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder().Print("hello").Sleep(TimeSpan.FromSeconds(1))) - .Build(); - - var inMemoryLog = new InMemoryLog(); - var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); - - finalResponse.State.Should().Be(ProcessState.Complete); - finalResponse.ExitCode.Should().Be(0); - - var allLogs = logs.JoinLogs(); - allLogs.Should().Contain("hello"); - completeScriptWasCalled.Should().BeTrue("The tests expects that the client actually called this"); - - inMemoryLog.ShouldNotHaveLoggedRetryAttemptsOrRetryFailures(); - } - - [Test] - [TentacleConfigurations] - public async Task WhenANetworkFailureOccurs_DuringCancelScript_TheClientIsAbleToSuccessfullyCancelTheScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var cts = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); - using var tmp = new TemporaryDirectory(); - var scriptIsRunningFlag = Path.Combine(tmp.DirectoryPath, "scriptisrunning"); - - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeGetStatus( - async () => - { - await Wait.For(() => File.Exists(scriptIsRunningFlag), - TimeSpan.FromSeconds(30), - () => throw new Exception("Script did not start"), - CancellationToken); - cts.Cancel(); - }) - .BeforeCancelScript( - async () => - { - await Task.CompletedTask; - - if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).LastException == null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - })) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("hello") - .CreateFile(scriptIsRunningFlag) - .Sleep(TimeSpan.FromMinutes(2)) - .Print("AllDone")) - .Build(); - - Exception? actualException = null; - var logs = new List(); - var inMemoryLog = new InMemoryLog(); - - try - { - await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, - onScriptStatusResponseReceived => - { - logs.AddRange(onScriptStatusResponseReceived.Logs); - }, - _ => Task.CompletedTask, - new SerilogLoggerBuilder().Build().ForContext().ToITentacleTaskLog().Chain(inMemoryLog), - cts.Token); - } - catch (Exception ex) - { - actualException = ex; - } - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); - - actualException!.ShouldMatchExceptionContract(expectedException); - - var allLogs = logs.JoinLogs(); - allLogs.Should().Contain("hello"); - allLogs.Should().NotContain("AllDone"); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).LastException.Should().NotBeNull(); - - inMemoryLog.ShouldHaveLoggedRetryAttemptsAndNoRetryFailures(); - } - - [Test] - [TentacleConfigurations] - public async Task WhenANetworkFailureOccurs_DuringGetCapabilities_TheClientIsAbleToSuccessfullyCompleteTheScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .DecorateCapabilitiesServiceV2With(d => d - .BeforeGetCapabilities( - async () => - { - if (recordedUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).LastException == null) - { - await tcpConnectionUtilities.RestartTcpConnection(); - - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - })) - .Build()) - .Build(CancellationToken); - - var inMemoryLog = new InMemoryLog(); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder().Print("hello")) - .Build(); - - var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); - - finalResponse.State.Should().Be(ProcessState.Complete); - finalResponse.ExitCode.Should().Be(0); - - var allLogs = logs.JoinLogs(); - allLogs.Should().Contain("hello"); - - recordedUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).Started.Should().Be(2); - - inMemoryLog.ShouldHaveLoggedRetryAttemptsAndNoRetryFailures(); - } - } -} +// using System; +// using System.Collections.Generic; +// using System.IO; +// using System.Threading; +// using System.Threading.Tasks; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.Client; +// using Octopus.Tentacle.CommonTestUtils; +// using Octopus.Tentacle.CommonTestUtils.Diagnostics; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; +// using Octopus.TestPortForwarder; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ClientScriptExecutionCanRecoverFromNetworkIssues : IntegrationTest +// { +// [Test] +// [TentacleConfigurations] +// public async Task WhenANetworkFailureOccurs_DuringStartScript_TheClientIsAbleToSuccessfullyCompleteTheScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPortForwarder() +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .Build()) +// .Build(CancellationToken); +// +// var scriptHasStartFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "scripthasstarted"); +// var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .CreateFile(scriptHasStartFile) +// .WaitForFileToExist(waitForFile) +// .Print("hello")) +// // Configure the start script command to wait a long time, so we have plenty of time to kill the connection. +// .WithDurationStartScriptCanWaitForScriptToFinish(TimeSpan.FromHours(1)) +// .Build(); +// +// var inMemoryLog = new InMemoryLog(); +// +// var execScriptTask = Task.Run( +// async () => await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog), +// CancellationToken); +// +// // Wait for the script to start. +// await Wait.For(() => File.Exists(scriptHasStartFile), +// TimeSpan.FromSeconds(30), +// () => throw new Exception("Script did not start"), +// CancellationToken); +// +// // Now it has started, kill active connections killing the start script request. +// clientTentacle.PortForwarder.CloseExistingConnections(); +// +// // Let the script finish. +// File.WriteAllText(waitForFile, ""); +// +// var (finalResponse, logs) = await execScriptTask; +// +// finalResponse.State.Should().Be(ProcessState.Complete); +// finalResponse.ExitCode.Should().Be(0); +// +// var allLogs = logs.JoinLogs(); +// allLogs.Should().Contain("hello"); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().BeGreaterThan(1); +// +// inMemoryLog.ShouldHaveLoggedRetryAttemptsAndNoRetryFailures(); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenANetworkFailureOccurs_DuringGetStatus_TheClientIsAbleToSuccessfullyCompleteTheScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeGetStatus( +// async () => +// { +// await Task.CompletedTask; +// +// if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException is null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("hello") +// .WaitForFileToExist(waitForFile) +// .Print("AllDone")) +// .Build(); +// +// var inMemoryLog = new InMemoryLog(); +// +// var execScriptTask = Task.Run( +// async () => await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog), +// CancellationToken); +// +// await Wait.For(() => recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException != null, +// TimeSpan.FromSeconds(60), +// () => throw new Exception("GetStatus did not error"), +// CancellationToken); +// +// // Let the script finish. +// File.WriteAllText(waitForFile, ""); +// +// var (finalResponse, logs) = await execScriptTask; +// +// finalResponse.State.Should().Be(ProcessState.Complete); +// finalResponse.ExitCode.Should().Be(0); +// +// var allLogs = logs.JoinLogs(); +// allLogs.Should().Contain("hello"); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException.Should().NotBeNull(); +// +// inMemoryLog.ShouldHaveLoggedRetryAttemptsAndNoRetryFailures(); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenANetworkFailureOccurs_DuringCompleteScript_TheClientIsAbleToSuccessfullyCompleteTheScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// bool completeScriptWasCalled = false; +// PortForwarder? portForwarder = null; +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPortForwarderDataLogging() +// .WithServiceEndpointModifier(serviceEndpoint => +// { +// serviceEndpoint.PollingRequestQueueTimeout = TimeSpan.FromSeconds(10); +// serviceEndpoint.RetryCountLimit = 1; +// }) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .DecorateAllScriptServicesWith(u => u +// .BeforeCompleteScript( +// async () => +// { +// +// await Task.CompletedTask; +// +// completeScriptWasCalled = true; +// // A successfully CompleteScript call is not required for the script to be completed. +// // So it should be the case that the tentacle can be no longer contactable at this point, +// // yet the script execution is marked as successful. +// portForwarder.Dispose(); +// })) +// .Build()) +// .Build(CancellationToken); +// portForwarder = clientTentacle.PortForwarder; +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder().Print("hello").Sleep(TimeSpan.FromSeconds(1))) +// .Build(); +// +// var inMemoryLog = new InMemoryLog(); +// var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); +// +// finalResponse.State.Should().Be(ProcessState.Complete); +// finalResponse.ExitCode.Should().Be(0); +// +// var allLogs = logs.JoinLogs(); +// allLogs.Should().Contain("hello"); +// completeScriptWasCalled.Should().BeTrue("The tests expects that the client actually called this"); +// +// inMemoryLog.ShouldNotHaveLoggedRetryAttemptsOrRetryFailures(); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenANetworkFailureOccurs_DuringCancelScript_TheClientIsAbleToSuccessfullyCancelTheScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var cts = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); +// using var tmp = new TemporaryDirectory(); +// var scriptIsRunningFlag = Path.Combine(tmp.DirectoryPath, "scriptisrunning"); +// +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeGetStatus( +// async () => +// { +// await Wait.For(() => File.Exists(scriptIsRunningFlag), +// TimeSpan.FromSeconds(30), +// () => throw new Exception("Script did not start"), +// CancellationToken); +// cts.Cancel(); +// }) +// .BeforeCancelScript( +// async () => +// { +// await Task.CompletedTask; +// +// if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).LastException == null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("hello") +// .CreateFile(scriptIsRunningFlag) +// .Sleep(TimeSpan.FromMinutes(2)) +// .Print("AllDone")) +// .Build(); +// +// Exception? actualException = null; +// var logs = new List(); +// var inMemoryLog = new InMemoryLog(); +// +// try +// { +// await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, +// onScriptStatusResponseReceived => +// { +// logs.AddRange(onScriptStatusResponseReceived.Logs); +// }, +// _ => Task.CompletedTask, +// new SerilogLoggerBuilder().Build().ForContext().ToITentacleTaskLog().Chain(inMemoryLog), +// cts.Token); +// } +// catch (Exception ex) +// { +// actualException = ex; +// } +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); +// +// actualException!.ShouldMatchExceptionContract(expectedException); +// +// var allLogs = logs.JoinLogs(); +// allLogs.Should().Contain("hello"); +// allLogs.Should().NotContain("AllDone"); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).LastException.Should().NotBeNull(); +// +// inMemoryLog.ShouldHaveLoggedRetryAttemptsAndNoRetryFailures(); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenANetworkFailureOccurs_DuringGetCapabilities_TheClientIsAbleToSuccessfullyCompleteTheScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .DecorateCapabilitiesServiceV2With(d => d +// .BeforeGetCapabilities( +// async () => +// { +// if (recordedUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).LastException == null) +// { +// await tcpConnectionUtilities.RestartTcpConnection(); +// +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var inMemoryLog = new InMemoryLog(); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder().Print("hello")) +// .Build(); +// +// var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); +// +// finalResponse.State.Should().Be(ProcessState.Complete); +// finalResponse.ExitCode.Should().Be(0); +// +// var allLogs = logs.JoinLogs(); +// allLogs.Should().Contain("hello"); +// +// recordedUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).Started.Should().Be(2); +// +// inMemoryLog.ShouldHaveLoggedRetryAttemptsAndNoRetryFailures(); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionIsolationMutex.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionIsolationMutex.cs index baf50164c..cdcac998b 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionIsolationMutex.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionIsolationMutex.cs @@ -1,182 +1,182 @@ -using System; -using System.Collections; -using System.IO; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ClientScriptExecutionIsolationMutex : IntegrationTest - { - [Test] - [TentacleConfigurations(testScriptIsolationLevelVersions: true, additionalParameterTypes: new object[] { typeof(ScriptIsolationLevel)})] - public async Task ScriptIsolationMutexFull_EnsuresTwoDifferentScriptsDontRunAtTheSameTime(TentacleConfigurationTestCase tentacleConfigurationTestCase, ScriptIsolationLevel levelOfSecondScript) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .Build()) - .Build(CancellationToken); - - var firstScriptStartFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "firstScriptStartFile"); - var firstScriptWaitFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "firstScriptWaitFile"); - - var secondScriptStart = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "secondScriptStartFile"); - - var firstStartScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .CreateFile(firstScriptStartFile) - .WaitForFileToExist(firstScriptWaitFile)) - .WithIsolationLevel(ScriptIsolationLevel.FullIsolation) - .WithIsolationMutexName("mymutex") - .Build(); - - var secondStartScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder().CreateFile(secondScriptStart)) - .WithIsolationLevel(levelOfSecondScript) - .WithIsolationMutexName("mymutex") - .Build(); - - var tentacleClient = clientTentacle.TentacleClient; - var firstScriptExecution = Task.Run(async () => await tentacleClient.ExecuteScript(firstStartScriptCommand, CancellationToken)); - - // Wait for the first script to start running - await Wait.For(() => File.Exists(firstScriptStartFile), - TimeSpan.FromSeconds(30), - () => throw new Exception("Script did not start"), - CancellationToken); - Logger.Information("First script is now running"); - - var secondScriptExecution = Task.Run(async () => await tentacleClient.ExecuteScript(secondStartScriptCommand, CancellationToken)); - - // Wait for the second script start script RPC call to return. - await Wait.For(() => recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Completed == 2, - TimeSpan.FromSeconds(60), - () => throw new Exception("Second execute script call did not complete"), - CancellationToken); - - // Give Tentacle some more time to run the script (although it should not). - await Task.Delay(TimeSpan.FromSeconds(2)); - - File.Exists(secondScriptStart).Should().BeFalse("The second script must not be started while the first is running with a FullIsolationMutex"); - - // Let the first script finish. - File.WriteAllText(firstScriptWaitFile, ""); - - var (finalResponseFirstScript, _) = await firstScriptExecution; - var (finalResponseSecondScript, _) = await secondScriptExecution; - - File.Exists(secondScriptStart).Should().BeTrue("The second should now have run."); - - finalResponseFirstScript.ExitCode.Should().Be(0); - finalResponseSecondScript.ExitCode.Should().Be(0); - } - - [Test] - [TentacleConfigurations(testScriptIsolationLevelVersions: true, additionalParameterTypes: new object[] {typeof(ScriptsInParallelTestCases)})] - public async Task ScriptIsolationMutexFull_IsOnlyExclusiveWhenFullAndWhenTheMutexNameIsTheSame(TentacleConfigurationTestCase tentacleConfigurationTestCase, ScriptsInParallelTestCase scriptsInParallelTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder().Build(CancellationToken); - - var firstScriptStartFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "firstScriptStartFile"); - var firstScriptWaitFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "firstScriptWaitFile"); - - var secondScriptStart = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "secondScriptStartFile"); - - var firstStartScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .CreateFile(firstScriptStartFile) - .WaitForFileToExist(firstScriptWaitFile)) - .WithIsolationLevel(scriptsInParallelTestCase.LevelOfFirstScript) - .WithIsolationMutexName(scriptsInParallelTestCase.MutexForFirstScript) - .Build(); - - var secondStartScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder().CreateFile(secondScriptStart)) - .WithIsolationLevel(scriptsInParallelTestCase.LevelOfSecondScript) - .WithIsolationMutexName(scriptsInParallelTestCase.MutexForSecondScript) - .Build(); - - var tentacleClient = clientTentacle.TentacleClient; - var firstScriptExecution = Task.Run(async () => await tentacleClient.ExecuteScript(firstStartScriptCommand, CancellationToken)); - - // Wait for the first script to start running - await Wait.For(() => File.Exists(firstScriptStartFile), - TimeSpan.FromSeconds(30), - () => throw new Exception("Script did not start running"), - CancellationToken); - - var secondScriptExecution = Task.Run(async () => await tentacleClient.ExecuteScript(secondStartScriptCommand, CancellationToken)); - - // Wait for the second script to start - await Wait.For(() => File.Exists(secondScriptStart), - TimeSpan.FromSeconds(30), - () => throw new Exception("Second script did not start"), - CancellationToken); - // Both scripts are now running in parallel - - // Let the first script finish. - File.WriteAllText(firstScriptWaitFile, ""); - - var (finalResponseFirstScript, _) = await firstScriptExecution; - var (finalResponseSecondScript, _) = await secondScriptExecution; - - File.Exists(secondScriptStart).Should().BeTrue("The second script must not be started while the first is running with a FullIsolationMutex"); - - finalResponseFirstScript.ExitCode.Should().Be(0); - finalResponseSecondScript.ExitCode.Should().Be(0); - } - - public class ScriptsInParallelTestCases : IEnumerable - { - public IEnumerator GetEnumerator() - { - // Scripts with the same mutex name can run at the same time if they both has no isolation. - yield return ScriptsInParallelTestCase.NoIsolationSameMutex; - // Scripts with different mutex names can run at the same time. - yield return ScriptsInParallelTestCase.FullIsolationDifferentMutex; - } - } - - public class ScriptsInParallelTestCase - { - public static ScriptsInParallelTestCase NoIsolationSameMutex => new(ScriptIsolationLevel.NoIsolation, "sameMutex", ScriptIsolationLevel.NoIsolation, "sameMutex", nameof(NoIsolationSameMutex)); - public static ScriptsInParallelTestCase FullIsolationDifferentMutex => new(ScriptIsolationLevel.FullIsolation, "mutex", ScriptIsolationLevel.FullIsolation, "differentMutex", nameof(FullIsolationDifferentMutex)); - - public readonly ScriptIsolationLevel LevelOfFirstScript; - public readonly string MutexForFirstScript; - public readonly ScriptIsolationLevel LevelOfSecondScript; - public readonly string MutexForSecondScript; - - private readonly string stringValue; - - private ScriptsInParallelTestCase( - ScriptIsolationLevel levelOfFirstScript, - string mutexForFirstScript, - ScriptIsolationLevel levelOfSecondScript, - string mutexForSecondScript, - string stringValue) - { - LevelOfFirstScript = levelOfFirstScript; - MutexForFirstScript = mutexForFirstScript; - LevelOfSecondScript = levelOfSecondScript; - MutexForSecondScript = mutexForSecondScript; - this.stringValue = stringValue; - } - - public override string ToString() - { - return stringValue; - } - } - } -} +// using System; +// using System.Collections; +// using System.IO; +// using System.Threading.Tasks; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ClientScriptExecutionIsolationMutex : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(testScriptIsolationLevelVersions: true, additionalParameterTypes: new object[] { typeof(ScriptIsolationLevel)})] +// public async Task ScriptIsolationMutexFull_EnsuresTwoDifferentScriptsDontRunAtTheSameTime(TentacleConfigurationTestCase tentacleConfigurationTestCase, ScriptIsolationLevel levelOfSecondScript) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .Build()) +// .Build(CancellationToken); +// +// var firstScriptStartFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "firstScriptStartFile"); +// var firstScriptWaitFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "firstScriptWaitFile"); +// +// var secondScriptStart = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "secondScriptStartFile"); +// +// var firstStartScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .CreateFile(firstScriptStartFile) +// .WaitForFileToExist(firstScriptWaitFile)) +// .WithIsolationLevel(ScriptIsolationLevel.FullIsolation) +// .WithIsolationMutexName("mymutex") +// .Build(); +// +// var secondStartScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder().CreateFile(secondScriptStart)) +// .WithIsolationLevel(levelOfSecondScript) +// .WithIsolationMutexName("mymutex") +// .Build(); +// +// var tentacleClient = clientTentacle.TentacleClient; +// var firstScriptExecution = Task.Run(async () => await tentacleClient.ExecuteScript(firstStartScriptCommand, CancellationToken)); +// +// // Wait for the first script to start running +// await Wait.For(() => File.Exists(firstScriptStartFile), +// TimeSpan.FromSeconds(30), +// () => throw new Exception("Script did not start"), +// CancellationToken); +// Logger.Information("First script is now running"); +// +// var secondScriptExecution = Task.Run(async () => await tentacleClient.ExecuteScript(secondStartScriptCommand, CancellationToken)); +// +// // Wait for the second script start script RPC call to return. +// await Wait.For(() => recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Completed == 2, +// TimeSpan.FromSeconds(60), +// () => throw new Exception("Second execute script call did not complete"), +// CancellationToken); +// +// // Give Tentacle some more time to run the script (although it should not). +// await Task.Delay(TimeSpan.FromSeconds(2)); +// +// File.Exists(secondScriptStart).Should().BeFalse("The second script must not be started while the first is running with a FullIsolationMutex"); +// +// // Let the first script finish. +// File.WriteAllText(firstScriptWaitFile, ""); +// +// var (finalResponseFirstScript, _) = await firstScriptExecution; +// var (finalResponseSecondScript, _) = await secondScriptExecution; +// +// File.Exists(secondScriptStart).Should().BeTrue("The second should now have run."); +// +// finalResponseFirstScript.ExitCode.Should().Be(0); +// finalResponseSecondScript.ExitCode.Should().Be(0); +// } +// +// [Test] +// [TentacleConfigurations(testScriptIsolationLevelVersions: true, additionalParameterTypes: new object[] {typeof(ScriptsInParallelTestCases)})] +// public async Task ScriptIsolationMutexFull_IsOnlyExclusiveWhenFullAndWhenTheMutexNameIsTheSame(TentacleConfigurationTestCase tentacleConfigurationTestCase, ScriptsInParallelTestCase scriptsInParallelTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder().Build(CancellationToken); +// +// var firstScriptStartFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "firstScriptStartFile"); +// var firstScriptWaitFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "firstScriptWaitFile"); +// +// var secondScriptStart = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "secondScriptStartFile"); +// +// var firstStartScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .CreateFile(firstScriptStartFile) +// .WaitForFileToExist(firstScriptWaitFile)) +// .WithIsolationLevel(scriptsInParallelTestCase.LevelOfFirstScript) +// .WithIsolationMutexName(scriptsInParallelTestCase.MutexForFirstScript) +// .Build(); +// +// var secondStartScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder().CreateFile(secondScriptStart)) +// .WithIsolationLevel(scriptsInParallelTestCase.LevelOfSecondScript) +// .WithIsolationMutexName(scriptsInParallelTestCase.MutexForSecondScript) +// .Build(); +// +// var tentacleClient = clientTentacle.TentacleClient; +// var firstScriptExecution = Task.Run(async () => await tentacleClient.ExecuteScript(firstStartScriptCommand, CancellationToken)); +// +// // Wait for the first script to start running +// await Wait.For(() => File.Exists(firstScriptStartFile), +// TimeSpan.FromSeconds(30), +// () => throw new Exception("Script did not start running"), +// CancellationToken); +// +// var secondScriptExecution = Task.Run(async () => await tentacleClient.ExecuteScript(secondStartScriptCommand, CancellationToken)); +// +// // Wait for the second script to start +// await Wait.For(() => File.Exists(secondScriptStart), +// TimeSpan.FromSeconds(30), +// () => throw new Exception("Second script did not start"), +// CancellationToken); +// // Both scripts are now running in parallel +// +// // Let the first script finish. +// File.WriteAllText(firstScriptWaitFile, ""); +// +// var (finalResponseFirstScript, _) = await firstScriptExecution; +// var (finalResponseSecondScript, _) = await secondScriptExecution; +// +// File.Exists(secondScriptStart).Should().BeTrue("The second script must not be started while the first is running with a FullIsolationMutex"); +// +// finalResponseFirstScript.ExitCode.Should().Be(0); +// finalResponseSecondScript.ExitCode.Should().Be(0); +// } +// +// public class ScriptsInParallelTestCases : IEnumerable +// { +// public IEnumerator GetEnumerator() +// { +// // Scripts with the same mutex name can run at the same time if they both has no isolation. +// yield return ScriptsInParallelTestCase.NoIsolationSameMutex; +// // Scripts with different mutex names can run at the same time. +// yield return ScriptsInParallelTestCase.FullIsolationDifferentMutex; +// } +// } +// +// public class ScriptsInParallelTestCase +// { +// public static ScriptsInParallelTestCase NoIsolationSameMutex => new(ScriptIsolationLevel.NoIsolation, "sameMutex", ScriptIsolationLevel.NoIsolation, "sameMutex", nameof(NoIsolationSameMutex)); +// public static ScriptsInParallelTestCase FullIsolationDifferentMutex => new(ScriptIsolationLevel.FullIsolation, "mutex", ScriptIsolationLevel.FullIsolation, "differentMutex", nameof(FullIsolationDifferentMutex)); +// +// public readonly ScriptIsolationLevel LevelOfFirstScript; +// public readonly string MutexForFirstScript; +// public readonly ScriptIsolationLevel LevelOfSecondScript; +// public readonly string MutexForSecondScript; +// +// private readonly string stringValue; +// +// private ScriptsInParallelTestCase( +// ScriptIsolationLevel levelOfFirstScript, +// string mutexForFirstScript, +// ScriptIsolationLevel levelOfSecondScript, +// string mutexForSecondScript, +// string stringValue) +// { +// LevelOfFirstScript = levelOfFirstScript; +// MutexForFirstScript = mutexForFirstScript; +// LevelOfSecondScript = levelOfSecondScript; +// MutexForSecondScript = mutexForSecondScript; +// this.stringValue = stringValue; +// } +// +// public override string ToString() +// { +// return stringValue; +// } +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionRetriesTimeout.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionRetriesTimeout.cs index edfb8893e..c0f319492 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionRetriesTimeout.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionRetriesTimeout.cs @@ -1,546 +1,546 @@ -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils.Diagnostics; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; - -namespace Octopus.Tentacle.Tests.Integration -{ - /// - /// These tests make sure that we can cancel or walk away (if code does not cooperate with cancellation tokens) - /// from RPC calls when they are being retried and the rpc timeout period elapses. - /// - [IntegrationTestTimeout] - public class ClientScriptExecutionRetriesTimeout : IntegrationTest - { - readonly TimeSpan retryIfRemainingDurationAtLeastBuffer = TimeSpan.FromSeconds(1); - readonly TimeSpan retryBackoffBuffer = TimeSpan.FromSeconds(2); - - [Test] - [TentacleConfigurations(additionalParameterTypes: new object[] {typeof(RpcCallStage)})] - public async Task WhenRpcRetriesTimeOut_DuringGetCapabilities_TheRpcCallIsCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) - { - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - // Set a short retry duration so we cancel fairly quickly - .WithRetryDuration(TimeSpan.FromSeconds(15)) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithPortForwarder(out var portForwarder) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var capabilitiesMethodUsages) - .RecordMethodUsages(tentacleConfigurationTestCase, out var scriptMethodUsages) - .DecorateCapabilitiesServiceV2With(d => d - .BeforeGetCapabilities( - async () => - { - // Kill the first GetCapabilities call to force the rpc call into retries - if (capabilitiesMethodUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).LastException is null) - { - // Ensure there is an active connection so it can be killed correctly - await tcpConnectionUtilities.RestartTcpConnection(); - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - else - { - if (rpcCallStage == RpcCallStage.Connecting) - { - // Kill the port forwarder so the next requests are in the connecting state when retries timeout - Logger.Information("Killing PortForwarder"); - portForwarder.Value.EnterKillNewAndExistingConnectionsMode(); - } - else - { - // Ensure there is an active connection so it can be killed correctly - await tcpConnectionUtilities.RestartTcpConnection(); - // Pause the port forwarder so the next requests are in-flight when retries timeout - responseMessageTcpKiller.PauseConnectionOnNextResponse(); - } - } - })) - .Build()) - .Build(CancellationToken); - - var inMemoryLog = new InMemoryLog(); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("Should not run this script")) - .Build(); - - var duration = Stopwatch.StartNew(); - - var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - duration.Stop(); - - capabilitiesMethodUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).Started.Should().BeGreaterOrEqualTo(2); - scriptMethodUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(0, "Test should not have not proceeded past GetCapabilities"); - - // Ensure we actually waited and retried until the timeout policy kicked in - duration.Elapsed.Should().BeGreaterOrEqualTo(clientAndTentacle.RpcRetrySettings.RetryDuration - retryIfRemainingDurationAtLeastBuffer - retryBackoffBuffer); - - inMemoryLog.ShouldHaveLoggedRetryAttemptsAndRetryFailure(); - } - - [Test] - [TentacleConfigurations] - public async Task WhenGetCapabilitiesFails_AndTakesLongerThanTheRetryDuration_TheCallIsNotRetried_AndTimesOut(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var retryDuration = TimeSpan.FromSeconds(15); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - // Set a short retry duration so we cancel fairly quickly - .WithRetryDuration(retryDuration) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var capabilitiesMethodUsages) - .RecordMethodUsages(tentacleConfigurationTestCase, out var scriptMethodUsages) - .DecorateCapabilitiesServiceV2With(d => d - .BeforeGetCapabilities( - async () => - { - await tcpConnectionUtilities.RestartTcpConnection(); - - // Sleep to make the initial RPC call take longer than the allowed retry duration - await Task.Delay(retryDuration + TimeSpan.FromSeconds(1)); - - // Kill the first GetCapabilities call to force the rpc call into retries - responseMessageTcpKiller.KillConnectionOnNextResponse(); - })) - .Build()) - .Build(CancellationToken); - - var inMemoryLog = new InMemoryLog(); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .Build(); - - var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - capabilitiesMethodUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).Started.Should().Be(1); - scriptMethodUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(0, "Test should not have not proceeded past GetCapabilities"); - - inMemoryLog.ShouldHaveLoggedRetryFailureAndNoRetryAttempts(); - } - - [Test] - [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage)})] - public async Task WhenRpcRetriesTimeOut_DuringStartScript_TheRpcCallIsCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) - { - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - // Set a short retry duration so we cancel fairly quickly - .WithRetryDuration(TimeSpan.FromSeconds(15)) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithPortForwarder(out var portForwarder) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeStartScript( - async () => - { - // Kill the first StartScript call to force the rpc call into retries - if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).LastException == null) - { - // Ensure there is an active connection so it can be killed correctly - await tcpConnectionUtilities.RestartTcpConnection(); - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - else - { - if (rpcCallStage == RpcCallStage.Connecting) - { - // Kill the port forwarder so the next requests are in the connecting state when retries timeout - Logger.Information("Killing PortForwarder"); - portForwarder.Value.EnterKillNewAndExistingConnectionsMode(); - } - else - { - // Ensure there is an active connection so it can be killed correctly - await tcpConnectionUtilities.RestartTcpConnection(); - // Pause the port forwarder so the next requests are in-flight when retries timeout - responseMessageTcpKiller.PauseConnectionOnNextResponse(); - } - } - })) - .Build()) - .Build(CancellationToken); - - var inMemoryLog = new InMemoryLog(); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("Start Script") - .Sleep(TimeSpan.FromHours(1)) - .Print("End Script")) - .Build(); - - var duration = Stopwatch.StartNew(); - var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - duration.Stop(); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().BeGreaterOrEqualTo(2); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(0); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); - - // Ensure we actually waited and retried until the timeout policy kicked in - duration.Elapsed.Should().BeGreaterOrEqualTo(clientAndTentacle.RpcRetrySettings.RetryDuration - retryIfRemainingDurationAtLeastBuffer - retryBackoffBuffer); - - inMemoryLog.ShouldHaveLoggedRetryAttemptsAndRetryFailure(); - } - - [Test] - [TentacleConfigurations] - public async Task WhenStartScriptFails_AndTakesLongerThanTheRetryDuration_TheCallIsNotRetried_AndTimesOut(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var retryDuration = TimeSpan.FromSeconds(15); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - // Set a short retry duration so we cancel fairly quickly - .WithRetryDuration(retryDuration) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeStartScript( - async () => - { - // Sleep to make the initial RPC call take longer than the allowed retry duration - await Task.Delay(retryDuration + TimeSpan.FromSeconds(1)); - - // Kill the first StartScript call to force the rpc call into retries - responseMessageTcpKiller.KillConnectionOnNextResponse(); - })) - .Build()) - .Build(CancellationToken); - - var inMemoryLog = new InMemoryLog(); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Sleep(TimeSpan.FromHours(1))) - .Build(); - - var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(0); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); - - inMemoryLog.ShouldHaveLoggedRetryFailureAndNoRetryAttempts(); - } - - [Test] - [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage)})] - public async Task WhenRpcRetriesTimeOut_DuringGetStatus_TheRpcCallIsCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) - { - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - // Set a short retry duration so we cancel fairly quickly - .WithRetryDuration(TimeSpan.FromSeconds(15)) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithPortForwarder(out var portForwarder) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeGetStatus( - async () => - { - // Kill the first GetStatus call to force the rpc call into retries - if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException == null) - { - // Ensure there is an active connection so it can be killed correctly - await tcpConnectionUtilities.RestartTcpConnection(); - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - else - { - if (rpcCallStage == RpcCallStage.Connecting) - { - // Kill the port forwarder so the next requests are in the connecting state when retries timeout - Logger.Information("Killing PortForwarder"); - portForwarder.Value.EnterKillNewAndExistingConnectionsMode(); - } - else - { - // Ensure there is an active connection so it can be killed correctly - await tcpConnectionUtilities.RestartTcpConnection(); - // Pause the port forwarder so the next requests are in-flight when retries timeout - responseMessageTcpKiller.PauseConnectionOnNextResponse(); - } - } - })) - .Build()) - .Build(CancellationToken); - - var inMemoryLog = new InMemoryLog(); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("Start Script") - .Sleep(TimeSpan.FromHours(1)) - .Print("End Script")) - // Don't wait in start script as we want to tst get status - .WithDurationStartScriptCanWaitForScriptToFinish(null) - .Build(); - - var duration = Stopwatch.StartNew(); - var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - duration.Stop(); - - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().BeGreaterOrEqualTo(2); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); - - // Ensure we actually waited and retried until the timeout policy kicked in - duration.Elapsed.Should().BeGreaterOrEqualTo(clientAndTentacle.RpcRetrySettings.RetryDuration - retryIfRemainingDurationAtLeastBuffer - retryBackoffBuffer); - - inMemoryLog.ShouldHaveLoggedRetryAttemptsAndRetryFailure(); - } - - [Test] - [TentacleConfigurations] - public async Task WhenGetStatusFails_AndTakesLongerThanTheRetryDuration_TheCallIsNotRetried_AndTimesOut(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var retryDuration = TimeSpan.FromSeconds(15); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - // Set a short retry duration so we cancel fairly quickly - .WithRetryDuration(retryDuration) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeGetStatus( - async () => - { - // Sleep to make the initial RPC call take longer than the allowed retry duration - await Task.Delay(retryDuration + TimeSpan.FromSeconds(1)); - - // Kill the first GetStatus call to force the rpc call into retries - responseMessageTcpKiller.KillConnectionOnNextResponse(); - })) - .Build()) - .Build(CancellationToken); - - var inMemoryLog = new InMemoryLog(); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Sleep(TimeSpan.FromHours(1))) - // Don't wait in start script as we want to test get status - .WithDurationStartScriptCanWaitForScriptToFinish(null) - .Build(); - - var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); - - inMemoryLog.ShouldHaveLoggedRetryFailureAndNoRetryAttempts(); - } - - [Test] - [TentacleConfigurations(additionalParameterTypes: new object[] {typeof(RpcCallStage)})] - public async Task WhenRpcRetriesTimeOut_DuringCancelScript_TheRpcCallIsCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) - { - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - // Set a short retry duration so we cancel fairly quickly - .WithRetryDuration(TimeSpan.FromSeconds(15)) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithPortForwarder(out var portForwarder) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeCancelScript( - async () => - { - // Kill the first CancelScript call to force the rpc call into retries - if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).LastException == null) - { - // Ensure there is an active connection so it can be killed correctly - await tcpConnectionUtilities.RestartTcpConnection(); - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - else - { - if (rpcCallStage == RpcCallStage.Connecting) - { - // Kill the port forwarder so the next requests are in the connecting state when retries timeout - Logger.Information("Killing PortForwarder"); - portForwarder.Value.EnterKillNewAndExistingConnectionsMode(); - } - else - { - // Ensure there is an active connection so it can be killed correctly - await tcpConnectionUtilities.RestartTcpConnection(); - // Pause the port forwarder so the next requests are in-flight when retries timeout - responseMessageTcpKiller.PauseConnectionOnNextResponse(); - } - } - })) - .Build()) - .Build(CancellationToken); - - var inMemoryLog = new InMemoryLog(); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Print("Start Script") - .Sleep(TimeSpan.FromHours(1)) - .Print("End Script")) - // Don't wait in start script as we want to tst get status - .WithDurationStartScriptCanWaitForScriptToFinish(null) - .Build(); - - // Start the script which will wait for a file to exist - var testCancellationTokenSource = new CancellationTokenSource(); - - var duration = Stopwatch.StartNew(); - var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript( - startScriptCommand, - CancellationTokenSource.CreateLinkedTokenSource(CancellationToken, testCancellationTokenSource.Token).Token, - null, - inMemoryLog); - - await Wait.For(() => recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Completed > 0, - TimeSpan.FromSeconds(60), - () => throw new Exception("Script execution did not complete"), - CancellationToken); - - // We cancel script execution via the cancellation token. This should trigger the CancelScript RPC call to be made - testCancellationTokenSource.Cancel(); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - duration.Stop(); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().BeGreaterOrEqualTo(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().BeGreaterOrEqualTo(2); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); - - // Ensure we actually waited and retried until the timeout policy kicked in - duration.Elapsed.Should().BeGreaterOrEqualTo(clientAndTentacle.RpcRetrySettings.RetryDuration - retryIfRemainingDurationAtLeastBuffer - retryBackoffBuffer); - - inMemoryLog.ShouldHaveLoggedRetryAttemptsAndRetryFailure(); - } - - [Test] - [TentacleConfigurations] - public async Task WhenCancelScriptFails_AndTakesLongerThanTheRetryDuration_TheCallIsNotRetried_AndTimesOut(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var retryDuration = TimeSpan.FromSeconds(15); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - // Set a short retry duration so we cancel fairly quickly - .WithRetryDuration(retryDuration) - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeCancelScript( - async () => - { - // Sleep to make the initial RPC call take longer than the allowed retry duration - await Task.Delay(retryDuration + TimeSpan.FromSeconds(1)); - - // Kill the first CancelScript call to force the rpc call into retries - responseMessageTcpKiller.KillConnectionOnNextResponse(); - })) - .Build()) - .Build(CancellationToken); - - var inMemoryLog = new InMemoryLog(); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b - .Sleep(TimeSpan.FromHours(1))) - // Don't wait in start script as we want to test get status - .WithDurationStartScriptCanWaitForScriptToFinish(null) - .Build(); - - // Start the script which will wait for a file to exist - var testCancellationTokenSource = new CancellationTokenSource(); - - var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript( - startScriptCommand, - CancellationTokenSource.CreateLinkedTokenSource(CancellationToken, testCancellationTokenSource.Token).Token, - null, - inMemoryLog); - - Func action = async () => await executeScriptTask; - await Wait.For( - () => recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Completed > 0, - TimeSpan.FromSeconds(60), - () => throw new Exception("Script Execution did not complete"), - CancellationToken); - - // We cancel script execution via the cancellation token. This should trigger the CancelScript RPC call to be made - testCancellationTokenSource.Cancel(); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().BeGreaterOrEqualTo(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); - - inMemoryLog.ShouldHaveLoggedRetryFailureAndNoRetryAttempts(); - } - } -} +// using System; +// using System.Diagnostics; +// using System.Threading; +// using System.Threading.Tasks; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils.Diagnostics; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// /// +// /// These tests make sure that we can cancel or walk away (if code does not cooperate with cancellation tokens) +// /// from RPC calls when they are being retried and the rpc timeout period elapses. +// /// +// [IntegrationTestTimeout] +// public class ClientScriptExecutionRetriesTimeout : IntegrationTest +// { +// readonly TimeSpan retryIfRemainingDurationAtLeastBuffer = TimeSpan.FromSeconds(1); +// readonly TimeSpan retryBackoffBuffer = TimeSpan.FromSeconds(2); +// +// [Test] +// [TentacleConfigurations(additionalParameterTypes: new object[] {typeof(RpcCallStage)})] +// public async Task WhenRpcRetriesTimeOut_DuringGetCapabilities_TheRpcCallIsCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) +// { +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// // Set a short retry duration so we cancel fairly quickly +// .WithRetryDuration(TimeSpan.FromSeconds(15)) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithPortForwarder(out var portForwarder) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var capabilitiesMethodUsages) +// .RecordMethodUsages(tentacleConfigurationTestCase, out var scriptMethodUsages) +// .DecorateCapabilitiesServiceV2With(d => d +// .BeforeGetCapabilities( +// async () => +// { +// // Kill the first GetCapabilities call to force the rpc call into retries +// if (capabilitiesMethodUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).LastException is null) +// { +// // Ensure there is an active connection so it can be killed correctly +// await tcpConnectionUtilities.RestartTcpConnection(); +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// else +// { +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// // Kill the port forwarder so the next requests are in the connecting state when retries timeout +// Logger.Information("Killing PortForwarder"); +// portForwarder.Value.EnterKillNewAndExistingConnectionsMode(); +// } +// else +// { +// // Ensure there is an active connection so it can be killed correctly +// await tcpConnectionUtilities.RestartTcpConnection(); +// // Pause the port forwarder so the next requests are in-flight when retries timeout +// responseMessageTcpKiller.PauseConnectionOnNextResponse(); +// } +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var inMemoryLog = new InMemoryLog(); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("Should not run this script")) +// .Build(); +// +// var duration = Stopwatch.StartNew(); +// +// var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// duration.Stop(); +// +// capabilitiesMethodUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).Started.Should().BeGreaterOrEqualTo(2); +// scriptMethodUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(0, "Test should not have not proceeded past GetCapabilities"); +// +// // Ensure we actually waited and retried until the timeout policy kicked in +// duration.Elapsed.Should().BeGreaterOrEqualTo(clientAndTentacle.RpcRetrySettings.RetryDuration - retryIfRemainingDurationAtLeastBuffer - retryBackoffBuffer); +// +// inMemoryLog.ShouldHaveLoggedRetryAttemptsAndRetryFailure(); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenGetCapabilitiesFails_AndTakesLongerThanTheRetryDuration_TheCallIsNotRetried_AndTimesOut(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var retryDuration = TimeSpan.FromSeconds(15); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// // Set a short retry duration so we cancel fairly quickly +// .WithRetryDuration(retryDuration) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var capabilitiesMethodUsages) +// .RecordMethodUsages(tentacleConfigurationTestCase, out var scriptMethodUsages) +// .DecorateCapabilitiesServiceV2With(d => d +// .BeforeGetCapabilities( +// async () => +// { +// await tcpConnectionUtilities.RestartTcpConnection(); +// +// // Sleep to make the initial RPC call take longer than the allowed retry duration +// await Task.Delay(retryDuration + TimeSpan.FromSeconds(1)); +// +// // Kill the first GetCapabilities call to force the rpc call into retries +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// })) +// .Build()) +// .Build(CancellationToken); +// +// var inMemoryLog = new InMemoryLog(); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .Build(); +// +// var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// capabilitiesMethodUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).Started.Should().Be(1); +// scriptMethodUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(0, "Test should not have not proceeded past GetCapabilities"); +// +// inMemoryLog.ShouldHaveLoggedRetryFailureAndNoRetryAttempts(); +// } +// +// [Test] +// [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage)})] +// public async Task WhenRpcRetriesTimeOut_DuringStartScript_TheRpcCallIsCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) +// { +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// // Set a short retry duration so we cancel fairly quickly +// .WithRetryDuration(TimeSpan.FromSeconds(15)) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithPortForwarder(out var portForwarder) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeStartScript( +// async () => +// { +// // Kill the first StartScript call to force the rpc call into retries +// if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).LastException == null) +// { +// // Ensure there is an active connection so it can be killed correctly +// await tcpConnectionUtilities.RestartTcpConnection(); +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// else +// { +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// // Kill the port forwarder so the next requests are in the connecting state when retries timeout +// Logger.Information("Killing PortForwarder"); +// portForwarder.Value.EnterKillNewAndExistingConnectionsMode(); +// } +// else +// { +// // Ensure there is an active connection so it can be killed correctly +// await tcpConnectionUtilities.RestartTcpConnection(); +// // Pause the port forwarder so the next requests are in-flight when retries timeout +// responseMessageTcpKiller.PauseConnectionOnNextResponse(); +// } +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var inMemoryLog = new InMemoryLog(); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("Start Script") +// .Sleep(TimeSpan.FromHours(1)) +// .Print("End Script")) +// .Build(); +// +// var duration = Stopwatch.StartNew(); +// var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// duration.Stop(); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().BeGreaterOrEqualTo(2); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(0); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); +// +// // Ensure we actually waited and retried until the timeout policy kicked in +// duration.Elapsed.Should().BeGreaterOrEqualTo(clientAndTentacle.RpcRetrySettings.RetryDuration - retryIfRemainingDurationAtLeastBuffer - retryBackoffBuffer); +// +// inMemoryLog.ShouldHaveLoggedRetryAttemptsAndRetryFailure(); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenStartScriptFails_AndTakesLongerThanTheRetryDuration_TheCallIsNotRetried_AndTimesOut(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var retryDuration = TimeSpan.FromSeconds(15); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// // Set a short retry duration so we cancel fairly quickly +// .WithRetryDuration(retryDuration) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeStartScript( +// async () => +// { +// // Sleep to make the initial RPC call take longer than the allowed retry duration +// await Task.Delay(retryDuration + TimeSpan.FromSeconds(1)); +// +// // Kill the first StartScript call to force the rpc call into retries +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// })) +// .Build()) +// .Build(CancellationToken); +// +// var inMemoryLog = new InMemoryLog(); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Sleep(TimeSpan.FromHours(1))) +// .Build(); +// +// var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(0); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); +// +// inMemoryLog.ShouldHaveLoggedRetryFailureAndNoRetryAttempts(); +// } +// +// [Test] +// [TentacleConfigurations(additionalParameterTypes: new object[] { typeof(RpcCallStage)})] +// public async Task WhenRpcRetriesTimeOut_DuringGetStatus_TheRpcCallIsCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) +// { +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// // Set a short retry duration so we cancel fairly quickly +// .WithRetryDuration(TimeSpan.FromSeconds(15)) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithPortForwarder(out var portForwarder) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeGetStatus( +// async () => +// { +// // Kill the first GetStatus call to force the rpc call into retries +// if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException == null) +// { +// // Ensure there is an active connection so it can be killed correctly +// await tcpConnectionUtilities.RestartTcpConnection(); +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// else +// { +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// // Kill the port forwarder so the next requests are in the connecting state when retries timeout +// Logger.Information("Killing PortForwarder"); +// portForwarder.Value.EnterKillNewAndExistingConnectionsMode(); +// } +// else +// { +// // Ensure there is an active connection so it can be killed correctly +// await tcpConnectionUtilities.RestartTcpConnection(); +// // Pause the port forwarder so the next requests are in-flight when retries timeout +// responseMessageTcpKiller.PauseConnectionOnNextResponse(); +// } +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var inMemoryLog = new InMemoryLog(); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("Start Script") +// .Sleep(TimeSpan.FromHours(1)) +// .Print("End Script")) +// // Don't wait in start script as we want to tst get status +// .WithDurationStartScriptCanWaitForScriptToFinish(null) +// .Build(); +// +// var duration = Stopwatch.StartNew(); +// var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// duration.Stop(); +// +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().BeGreaterOrEqualTo(2); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); +// +// // Ensure we actually waited and retried until the timeout policy kicked in +// duration.Elapsed.Should().BeGreaterOrEqualTo(clientAndTentacle.RpcRetrySettings.RetryDuration - retryIfRemainingDurationAtLeastBuffer - retryBackoffBuffer); +// +// inMemoryLog.ShouldHaveLoggedRetryAttemptsAndRetryFailure(); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenGetStatusFails_AndTakesLongerThanTheRetryDuration_TheCallIsNotRetried_AndTimesOut(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var retryDuration = TimeSpan.FromSeconds(15); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// // Set a short retry duration so we cancel fairly quickly +// .WithRetryDuration(retryDuration) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeGetStatus( +// async () => +// { +// // Sleep to make the initial RPC call take longer than the allowed retry duration +// await Task.Delay(retryDuration + TimeSpan.FromSeconds(1)); +// +// // Kill the first GetStatus call to force the rpc call into retries +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// })) +// .Build()) +// .Build(CancellationToken); +// +// var inMemoryLog = new InMemoryLog(); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Sleep(TimeSpan.FromHours(1))) +// // Don't wait in start script as we want to test get status +// .WithDurationStartScriptCanWaitForScriptToFinish(null) +// .Build(); +// +// var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); +// +// inMemoryLog.ShouldHaveLoggedRetryFailureAndNoRetryAttempts(); +// } +// +// [Test] +// [TentacleConfigurations(additionalParameterTypes: new object[] {typeof(RpcCallStage)})] +// public async Task WhenRpcRetriesTimeOut_DuringCancelScript_TheRpcCallIsCancelled(TentacleConfigurationTestCase tentacleConfigurationTestCase, RpcCallStage rpcCallStage) +// { +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// // Set a short retry duration so we cancel fairly quickly +// .WithRetryDuration(TimeSpan.FromSeconds(15)) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithPortForwarder(out var portForwarder) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeCancelScript( +// async () => +// { +// // Kill the first CancelScript call to force the rpc call into retries +// if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).LastException == null) +// { +// // Ensure there is an active connection so it can be killed correctly +// await tcpConnectionUtilities.RestartTcpConnection(); +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// else +// { +// if (rpcCallStage == RpcCallStage.Connecting) +// { +// // Kill the port forwarder so the next requests are in the connecting state when retries timeout +// Logger.Information("Killing PortForwarder"); +// portForwarder.Value.EnterKillNewAndExistingConnectionsMode(); +// } +// else +// { +// // Ensure there is an active connection so it can be killed correctly +// await tcpConnectionUtilities.RestartTcpConnection(); +// // Pause the port forwarder so the next requests are in-flight when retries timeout +// responseMessageTcpKiller.PauseConnectionOnNextResponse(); +// } +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var inMemoryLog = new InMemoryLog(); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Print("Start Script") +// .Sleep(TimeSpan.FromHours(1)) +// .Print("End Script")) +// // Don't wait in start script as we want to tst get status +// .WithDurationStartScriptCanWaitForScriptToFinish(null) +// .Build(); +// +// // Start the script which will wait for a file to exist +// var testCancellationTokenSource = new CancellationTokenSource(); +// +// var duration = Stopwatch.StartNew(); +// var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript( +// startScriptCommand, +// CancellationTokenSource.CreateLinkedTokenSource(CancellationToken, testCancellationTokenSource.Token).Token, +// null, +// inMemoryLog); +// +// await Wait.For(() => recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Completed > 0, +// TimeSpan.FromSeconds(60), +// () => throw new Exception("Script execution did not complete"), +// CancellationToken); +// +// // We cancel script execution via the cancellation token. This should trigger the CancelScript RPC call to be made +// testCancellationTokenSource.Cancel(); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// duration.Stop(); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().BeGreaterOrEqualTo(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().BeGreaterOrEqualTo(2); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); +// +// // Ensure we actually waited and retried until the timeout policy kicked in +// duration.Elapsed.Should().BeGreaterOrEqualTo(clientAndTentacle.RpcRetrySettings.RetryDuration - retryIfRemainingDurationAtLeastBuffer - retryBackoffBuffer); +// +// inMemoryLog.ShouldHaveLoggedRetryAttemptsAndRetryFailure(); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenCancelScriptFails_AndTakesLongerThanTheRetryDuration_TheCallIsNotRetried_AndTimesOut(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var retryDuration = TimeSpan.FromSeconds(15); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// // Set a short retry duration so we cancel fairly quickly +// .WithRetryDuration(retryDuration) +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeCancelScript( +// async () => +// { +// // Sleep to make the initial RPC call take longer than the allowed retry duration +// await Task.Delay(retryDuration + TimeSpan.FromSeconds(1)); +// +// // Kill the first CancelScript call to force the rpc call into retries +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// })) +// .Build()) +// .Build(CancellationToken); +// +// var inMemoryLog = new InMemoryLog(); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b +// .Sleep(TimeSpan.FromHours(1))) +// // Don't wait in start script as we want to test get status +// .WithDurationStartScriptCanWaitForScriptToFinish(null) +// .Build(); +// +// // Start the script which will wait for a file to exist +// var testCancellationTokenSource = new CancellationTokenSource(); +// +// var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript( +// startScriptCommand, +// CancellationTokenSource.CreateLinkedTokenSource(CancellationToken, testCancellationTokenSource.Token).Token, +// null, +// inMemoryLog); +// +// Func action = async () => await executeScriptTask; +// await Wait.For( +// () => recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Completed > 0, +// TimeSpan.FromSeconds(60), +// () => throw new Exception("Script Execution did not complete"), +// CancellationToken); +// +// // We cancel script execution via the cancellation token. This should trigger the CancelScript RPC call to be made +// testCancellationTokenSource.Cancel(); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().BeGreaterOrEqualTo(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); +// +// inMemoryLog.ShouldHaveLoggedRetryFailureAndNoRetryAttempts(); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptArgumentsWork.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptArgumentsWork.cs index cdce531e7..1127d47f0 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptArgumentsWork.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptArgumentsWork.cs @@ -1,41 +1,41 @@ -using System; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils.Builders; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ClientScriptExecutionScriptArgumentsWork : IntegrationTest - { - [Test] - [TentacleConfigurations(testCommonVersions: true)] - public async Task ArgumentsArePassedToTheScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder().Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder().PrintArguments()) - .WithArguments(new[] { "First", "Second", "AndSpacesAreNotHandledWellInTentacle" }) - .Build(); - - var tentacleServicesDecorator = new TentacleServiceDecoratorBuilder().Build(); - - var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - - finalResponse.State.Should().Be(ProcessState.Complete); - finalResponse.ExitCode.Should().Be(0); - - var allLogs = logs.JoinLogs(); - - allLogs.Should().MatchRegex(".*Argument: First\n.*Argument: Second\n.*Argument: AndSpacesAreNotHandledWellInTentacle\n"); - } - } -} \ No newline at end of file +// using System; +// using System.Threading.Tasks; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils.Builders; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ClientScriptExecutionScriptArgumentsWork : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(testCommonVersions: true)] +// public async Task ArgumentsArePassedToTheScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder().Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder().PrintArguments()) +// .WithArguments(new[] { "First", "Second", "AndSpacesAreNotHandledWellInTentacle" }) +// .Build(); +// +// var tentacleServicesDecorator = new TentacleServiceDecoratorBuilder().Build(); +// +// var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// +// finalResponse.State.Should().Be(ProcessState.Complete); +// finalResponse.ExitCode.Should().Be(0); +// +// var allLogs = logs.JoinLogs(); +// +// allLogs.Should().MatchRegex(".*Argument: First\n.*Argument: Second\n.*Argument: AndSpacesAreNotHandledWellInTentacle\n"); +// } +// } +// } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptFilesAreSent.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptFilesAreSent.cs index 181ceeef0..f5423050c 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptFilesAreSent.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptFilesAreSent.cs @@ -1,38 +1,38 @@ -using System; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils.Builders; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ClientScriptExecutionScriptFilesAreSent : IntegrationTest - { - [Test] - [TentacleConfigurations(testCommonVersions: true)] - public async Task ScriptFilesAreSent(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder().Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder().PrintFileContents("foo.txt")) - .WithScriptFile(new ScriptFile("foo.txt", DataStream.FromString("The File Contents"))) - .Build(); - - var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - - finalResponse.State.Should().Be(ProcessState.Complete); - finalResponse.ExitCode.Should().Be(0); - - var allLogs = logs.JoinLogs(); - - allLogs.Should().Contain("The File Contents"); - } - } -} +// using System; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils.Builders; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ClientScriptExecutionScriptFilesAreSent : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(testCommonVersions: true)] +// public async Task ScriptFilesAreSent(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder().Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder().PrintFileContents("foo.txt")) +// .WithScriptFile(new ScriptFile("foo.txt", DataStream.FromString("The File Contents"))) +// .Build(); +// +// var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// +// finalResponse.State.Should().Be(ProcessState.Complete); +// finalResponse.ExitCode.Should().Be(0); +// +// var allLogs = logs.JoinLogs(); +// +// allLogs.Should().Contain("The File Contents"); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceNonV1IsNotRetriedWhenRetriesAreDisabled.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceNonV1IsNotRetriedWhenRetriesAreDisabled.cs index 2986460e6..3cc14d6ca 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceNonV1IsNotRetriedWhenRetriesAreDisabled.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceNonV1IsNotRetriedWhenRetriesAreDisabled.cs @@ -1,292 +1,292 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils.Builders; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ClientScriptExecutionScriptServiceNonV1IsNotRetriedWhenRetriesAreDisabled : IntegrationTest - { - [Test] - [TentacleConfigurations] - public async Task WhenNetworkFailureOccurs_DuringGetCapabilities_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithRetriesDisabled() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithRetryDuration(TimeSpan.FromMinutes(4)) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var capabilitiesRecordedUsages) - .RecordMethodUsages(tentacleConfigurationTestCase, out var scriptRecordedUsages) - .DecorateCapabilitiesServiceV2With(d => d - .BeforeGetCapabilities( - async () => - { - // Due to the GetCapabilities response getting cached, we must - // use a different service to ensure Tentacle is connected to Server. - // Otherwise, the response to the 'ensure connection' will get cached - // and any subsequent calls will succeed w/o using the network. - await tcpConnectionUtilities.RestartTcpConnection(); - - if (capabilitiesRecordedUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).LastException is null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - })) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder().Print("hello")).Build(); - - var logs = new List(); - var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - var allLogs = logs.JoinLogs(); - - allLogs.Should().NotContain("hello"); - - var getCapabilitiesUsages = capabilitiesRecordedUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)); - getCapabilitiesUsages.LastException.Should().NotBeNull(); - getCapabilitiesUsages.Started.Should().Be(1); - getCapabilitiesUsages.Completed.Should().Be(1); - - scriptRecordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(0); - scriptRecordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Completed.Should().Be(0); - scriptRecordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(0); - scriptRecordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Completed.Should().Be(0); - } - - [Test] - [TentacleConfigurations] - public async Task WhenANetworkFailureOccurs_DuringStartScript_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithRetriesDisabled() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithRetryDuration(TimeSpan.FromMinutes(4)) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeStartScript( - async () => - { - await Task.CompletedTask; - - if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).LastException is null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - })) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("hello") - .Print("AllDone")) - .Build(); - - var logs = new List(); - var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - var allLogs = logs.JoinLogs(); - - allLogs.Should().NotContain("AllDone"); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(0); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); - - // We must ensure all script are complete, otherwise if we shutdown tentacle while running a script the build can hang. - // Ensure the script is finished by running the script again - await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, new List(), CancellationToken); - } - - [Test] - [TentacleConfigurations] - public async Task WhenANetworkFailureOccurs_DuringGetStatus_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithRetriesDisabled() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithRetryDuration(TimeSpan.FromMinutes(4)) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeGetStatus( - async () => - { - await Task.CompletedTask; - - if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException is null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - })) - .Build()) - .Build(CancellationToken); - - var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("hello") - .WaitForFileToExist(waitForFile) - .Print("AllDone")) - .Build(); - - var logs = new List(); - - var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - var allLogs = logs.JoinLogs(); - - allLogs.Should().NotContain("AllDone"); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); - - // Let the script finish. - File.WriteAllText(waitForFile, ""); - await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, new List(), CancellationToken); - } - - [Test] - [TentacleConfigurations] - public async Task WhenANetworkFailureOccurs_DuringCancelScript_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithRetriesDisabled() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithRetryDuration(TimeSpan.FromMinutes(4)) - .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeGetStatus( - async () => - { - await Task.CompletedTask; - - cts.Cancel(); - }) - .BeforeCancelScript( - async () => - { - if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).LastException is null) - { - await tcpConnectionUtilities.RestartTcpConnection(); - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - })) - .Build()) - .Build(CancellationToken); - - var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("hello") - .WaitForFileToExist(waitForFile) - .Print("AllDone")) - .Build(); - - var logs = new List(); - var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, cts.Token); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - var allLogs = logs.JoinLogs(); - - allLogs.Should().NotContain("AllDone"); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); - - // Let the script finish. - File.WriteAllText(waitForFile, ""); - await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, new List(), CancellationToken); - } - - [Test] - [TentacleConfigurations] - public async Task WhenANetworkFailureOccurs_DuringCompleteScript_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithRetriesDisabled() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithRetryDuration(TimeSpan.FromMinutes(4)) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateAllScriptServicesWith(u => u - .BeforeCompleteScript( - async () => - { - await Task.CompletedTask; - - if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).LastException is null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - })) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("hello") - .Print("AllDone")) - .Build(); - - var logs = new List(); - await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); - - var allLogs = logs.JoinLogs(); - allLogs.Should().Contain("AllDone"); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); - } - } -} \ No newline at end of file +// using System; +// using System.Collections.Generic; +// using System.IO; +// using System.Threading; +// using System.Threading.Tasks; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils.Builders; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ClientScriptExecutionScriptServiceNonV1IsNotRetriedWhenRetriesAreDisabled : IntegrationTest +// { +// [Test] +// [TentacleConfigurations] +// public async Task WhenNetworkFailureOccurs_DuringGetCapabilities_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithRetriesDisabled() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithRetryDuration(TimeSpan.FromMinutes(4)) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var capabilitiesRecordedUsages) +// .RecordMethodUsages(tentacleConfigurationTestCase, out var scriptRecordedUsages) +// .DecorateCapabilitiesServiceV2With(d => d +// .BeforeGetCapabilities( +// async () => +// { +// // Due to the GetCapabilities response getting cached, we must +// // use a different service to ensure Tentacle is connected to Server. +// // Otherwise, the response to the 'ensure connection' will get cached +// // and any subsequent calls will succeed w/o using the network. +// await tcpConnectionUtilities.RestartTcpConnection(); +// +// if (capabilitiesRecordedUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)).LastException is null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder().Print("hello")).Build(); +// +// var logs = new List(); +// var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// var allLogs = logs.JoinLogs(); +// +// allLogs.Should().NotContain("hello"); +// +// var getCapabilitiesUsages = capabilitiesRecordedUsages.For(nameof(IAsyncClientCapabilitiesServiceV2.GetCapabilitiesAsync)); +// getCapabilitiesUsages.LastException.Should().NotBeNull(); +// getCapabilitiesUsages.Started.Should().Be(1); +// getCapabilitiesUsages.Completed.Should().Be(1); +// +// scriptRecordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(0); +// scriptRecordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Completed.Should().Be(0); +// scriptRecordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(0); +// scriptRecordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Completed.Should().Be(0); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenANetworkFailureOccurs_DuringStartScript_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithRetriesDisabled() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithRetryDuration(TimeSpan.FromMinutes(4)) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeStartScript( +// async () => +// { +// await Task.CompletedTask; +// +// if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).LastException is null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("hello") +// .Print("AllDone")) +// .Build(); +// +// var logs = new List(); +// var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// var allLogs = logs.JoinLogs(); +// +// allLogs.Should().NotContain("AllDone"); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(0); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); +// +// // We must ensure all script are complete, otherwise if we shutdown tentacle while running a script the build can hang. +// // Ensure the script is finished by running the script again +// await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, new List(), CancellationToken); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenANetworkFailureOccurs_DuringGetStatus_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithRetriesDisabled() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithRetryDuration(TimeSpan.FromMinutes(4)) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeGetStatus( +// async () => +// { +// await Task.CompletedTask; +// +// if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException is null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("hello") +// .WaitForFileToExist(waitForFile) +// .Print("AllDone")) +// .Build(); +// +// var logs = new List(); +// +// var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// var allLogs = logs.JoinLogs(); +// +// allLogs.Should().NotContain("AllDone"); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); +// +// // Let the script finish. +// File.WriteAllText(waitForFile, ""); +// await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, new List(), CancellationToken); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenANetworkFailureOccurs_DuringCancelScript_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithRetriesDisabled() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithRetryDuration(TimeSpan.FromMinutes(4)) +// .WithTcpConnectionUtilities(Logger, out var tcpConnectionUtilities) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeGetStatus( +// async () => +// { +// await Task.CompletedTask; +// +// cts.Cancel(); +// }) +// .BeforeCancelScript( +// async () => +// { +// if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).LastException is null) +// { +// await tcpConnectionUtilities.RestartTcpConnection(); +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("hello") +// .WaitForFileToExist(waitForFile) +// .Print("AllDone")) +// .Build(); +// +// var logs = new List(); +// var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, cts.Token); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// var allLogs = logs.JoinLogs(); +// +// allLogs.Should().NotContain("AllDone"); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(0); +// +// // Let the script finish. +// File.WriteAllText(waitForFile, ""); +// await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, new List(), CancellationToken); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenANetworkFailureOccurs_DuringCompleteScript_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithRetriesDisabled() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithRetryDuration(TimeSpan.FromMinutes(4)) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateAllScriptServicesWith(u => u +// .BeforeCompleteScript( +// async () => +// { +// await Task.CompletedTask; +// +// if (recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).LastException is null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// })) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("hello") +// .Print("AllDone")) +// .Build(); +// +// var logs = new List(); +// await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); +// +// var allLogs = logs.JoinLogs(); +// allLogs.Should().Contain("AllDone"); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); +// } +// } +// } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceV1IsNotRetried.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceV1IsNotRetried.cs index 4d117c972..8be5b7864 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceV1IsNotRetried.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceV1IsNotRetried.cs @@ -1,251 +1,251 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ClientScriptExecutionScriptServiceV1IsNotRetried : IntegrationTest - { - [Test] - [TentacleConfigurations(testNoCapabilitiesServiceVersions: true)] - public async Task WhenANetworkFailureOccurs_DuringStartScript_WithATentacleThatOnlySupportsV1ScriptService_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithRetryDuration(TimeSpan.FromMinutes(4)) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .HookServiceMethod( - nameof(IAsyncClientScriptService.StartScriptAsync), - async (_, _) => - { - await Task.CompletedTask; - - if (recordedUsages.For(nameof(IAsyncClientScriptService.StartScriptAsync)).LastException == null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - }) - .Build()) - .Build(CancellationToken); - - var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("hello") - .WaitForFileToExist(waitForFile) - .Print("AllDone")) - .WithIsolationLevel(ScriptIsolationLevel.FullIsolation) - .WithIsolationMutexName("bob") - .Build(); - - var logs = new List(); - var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - // Let the script finish. - File.WriteAllText(waitForFile, ""); - - var allLogs = logs.JoinLogs(); - - allLogs.Should().NotContain("AllDone"); - recordedUsages.For(nameof(IAsyncClientScriptService.StartScriptAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientScriptService.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptService.GetStatusAsync)).Started.Should().Be(0); - recordedUsages.For(nameof(IAsyncClientScriptService.CompleteScriptAsync)).Started.Should().Be(0); - - // We must ensure all script are complete, otherwise if we shutdown tentacle while running a script the build can hang. - // Ensure the script is finished by running the script again, the isolation mutex will ensure this second script runs after the first is complete. - await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, new List(), CancellationToken); - } - - [Test] - [TentacleConfigurations(testNoCapabilitiesServiceVersions: true)] - public async Task WhenANetworkFailureOccurs_DuringGetStatus_WithATentacleThatOnlySupportsV1ScriptService_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - ScriptStatusRequest? scriptStatusRequest = null; - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithRetryDuration(TimeSpan.FromMinutes(4)) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .HookServiceMethod( - nameof(IAsyncClientScriptService.GetStatusAsync), - async (_, request) => - { - await Task.CompletedTask; - - scriptStatusRequest = request; - if (recordedUsages.For(nameof(IAsyncClientScriptService.GetStatusAsync)).LastException == null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - }) - .Build()) - .Build(CancellationToken); - - var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("hello") - .WaitForFileToExist(waitForFile) - .Print("AllDone")) - .Build(); - - var logs = new List(); - var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - // Let the script finish. - File.WriteAllText(waitForFile, ""); - var legacyTentacleClient = clientTentacle.LegacyTentacleClientBuilder().Build(); - - await Wait.For( - async () => (await legacyTentacleClient.ScriptService.GetStatusAsync(scriptStatusRequest, new(CancellationToken))).State == ProcessState.Complete, - TimeSpan.FromSeconds(60), - () => throw new Exception("Script Execution did not complete"), - CancellationToken); - - var allLogs = logs.JoinLogs(); - - allLogs.Should().NotContain("AllDone"); - recordedUsages.For(nameof(IAsyncClientScriptService.GetStatusAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientScriptService.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptService.GetStatusAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptService.CompleteScriptAsync)).Started.Should().Be(0); - } - - [Test] - [TentacleConfigurations(testNoCapabilitiesServiceVersions: true)] - public async Task WhenANetworkFailureOccurs_DuringCancelScript_WithATentacleThatOnlySupportsV1ScriptService_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - ScriptStatusRequest? scriptStatusRequest = null; - CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithRetryDuration(TimeSpan.FromMinutes(4)) - .WithPortForwarderDataLogging() - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .HookServiceMethod( - nameof(IAsyncClientScriptService.GetStatusAsync), - async (_, request) => - { - await Task.CompletedTask; - - cts.Cancel(); - scriptStatusRequest = request; - }) - .HookServiceMethod( - nameof(IAsyncClientScriptService.CancelScriptAsync), - async (_, _) => - { - await Task.CompletedTask; - - if (recordedUsages.For(nameof(IAsyncClientScriptService.CancelScriptAsync)).LastException == null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - }) - .Build()) - .Build(CancellationToken); - - var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("hello") - .WaitForFileToExist(waitForFile) - .Print("AllDone")) - .Build(); - - var logs = new List(); - - var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, cts.Token); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - var allLogs = logs.JoinLogs(); - allLogs.Should().NotContain("AllDone"); - recordedUsages.For(nameof(IAsyncClientScriptService.CancelScriptAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientScriptService.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptService.CancelScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptService.CompleteScriptAsync)).Started.Should().Be(0); - - // Let the script finish. - File.WriteAllText(waitForFile, ""); - var legacyTentacleClient = clientTentacle.LegacyTentacleClientBuilder().Build(); - - await Wait.For( - async () => (await legacyTentacleClient.ScriptService.GetStatusAsync(scriptStatusRequest, new(CancellationToken))).State == ProcessState.Complete, - TimeSpan.FromSeconds(60), - () => throw new Exception("Script Execution did not complete"), - CancellationToken); - } - - [Test] - [TentacleConfigurations(testNoCapabilitiesServiceVersions: true)] - public async Task WhenANetworkFailureOccurs_DuringCompleteScript_WithATentacleThatOnlySupportsV1ScriptService_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithPortForwarderDataLogging() - .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) - .WithRetryDuration(TimeSpan.FromMinutes(4)) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .HookServiceMethod( - nameof(IAsyncClientScriptService.CompleteScriptAsync), - async (_, _) => - { - await Task.CompletedTask; - - if (recordedUsages.For(nameof(IAsyncClientScriptService.CompleteScriptAsync)).LastException == null) - { - responseMessageTcpKiller.KillConnectionOnNextResponse(); - } - }) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder().SetScriptBody(new ScriptBuilder().Print("hello")).Build(); - - var logs = new List(); - var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); - - var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); - - await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); - - // We Can not verify what will be in the logs because of race conditions in tentacle. - // The last complete script which we fail might come back with the logs. - recordedUsages.For(nameof(IAsyncClientScriptService.CompleteScriptAsync)).LastException.Should().NotBeNull(); - recordedUsages.For(nameof(IAsyncClientScriptService.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptService.CompleteScriptAsync)).Started.Should().Be(1); - } - } -} \ No newline at end of file +// using System; +// using System.Collections.Generic; +// using System.IO; +// using System.Threading; +// using System.Threading.Tasks; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Util.TcpTentacleHelpers; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ClientScriptExecutionScriptServiceV1IsNotRetried : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(testNoCapabilitiesServiceVersions: true)] +// public async Task WhenANetworkFailureOccurs_DuringStartScript_WithATentacleThatOnlySupportsV1ScriptService_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithRetryDuration(TimeSpan.FromMinutes(4)) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .HookServiceMethod( +// nameof(IAsyncClientScriptService.StartScriptAsync), +// async (_, _) => +// { +// await Task.CompletedTask; +// +// if (recordedUsages.For(nameof(IAsyncClientScriptService.StartScriptAsync)).LastException == null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// }) +// .Build()) +// .Build(CancellationToken); +// +// var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("hello") +// .WaitForFileToExist(waitForFile) +// .Print("AllDone")) +// .WithIsolationLevel(ScriptIsolationLevel.FullIsolation) +// .WithIsolationMutexName("bob") +// .Build(); +// +// var logs = new List(); +// var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// // Let the script finish. +// File.WriteAllText(waitForFile, ""); +// +// var allLogs = logs.JoinLogs(); +// +// allLogs.Should().NotContain("AllDone"); +// recordedUsages.For(nameof(IAsyncClientScriptService.StartScriptAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientScriptService.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptService.GetStatusAsync)).Started.Should().Be(0); +// recordedUsages.For(nameof(IAsyncClientScriptService.CompleteScriptAsync)).Started.Should().Be(0); +// +// // We must ensure all script are complete, otherwise if we shutdown tentacle while running a script the build can hang. +// // Ensure the script is finished by running the script again, the isolation mutex will ensure this second script runs after the first is complete. +// await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, new List(), CancellationToken); +// } +// +// [Test] +// [TentacleConfigurations(testNoCapabilitiesServiceVersions: true)] +// public async Task WhenANetworkFailureOccurs_DuringGetStatus_WithATentacleThatOnlySupportsV1ScriptService_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// ScriptStatusRequest? scriptStatusRequest = null; +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithRetryDuration(TimeSpan.FromMinutes(4)) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .HookServiceMethod( +// nameof(IAsyncClientScriptService.GetStatusAsync), +// async (_, request) => +// { +// await Task.CompletedTask; +// +// scriptStatusRequest = request; +// if (recordedUsages.For(nameof(IAsyncClientScriptService.GetStatusAsync)).LastException == null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// }) +// .Build()) +// .Build(CancellationToken); +// +// var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("hello") +// .WaitForFileToExist(waitForFile) +// .Print("AllDone")) +// .Build(); +// +// var logs = new List(); +// var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// // Let the script finish. +// File.WriteAllText(waitForFile, ""); +// var legacyTentacleClient = clientTentacle.LegacyTentacleClientBuilder().Build(); +// +// await Wait.For( +// async () => (await legacyTentacleClient.ScriptService.GetStatusAsync(scriptStatusRequest, new(CancellationToken))).State == ProcessState.Complete, +// TimeSpan.FromSeconds(60), +// () => throw new Exception("Script Execution did not complete"), +// CancellationToken); +// +// var allLogs = logs.JoinLogs(); +// +// allLogs.Should().NotContain("AllDone"); +// recordedUsages.For(nameof(IAsyncClientScriptService.GetStatusAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientScriptService.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptService.GetStatusAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptService.CompleteScriptAsync)).Started.Should().Be(0); +// } +// +// [Test] +// [TentacleConfigurations(testNoCapabilitiesServiceVersions: true)] +// public async Task WhenANetworkFailureOccurs_DuringCancelScript_WithATentacleThatOnlySupportsV1ScriptService_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// ScriptStatusRequest? scriptStatusRequest = null; +// CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithRetryDuration(TimeSpan.FromMinutes(4)) +// .WithPortForwarderDataLogging() +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .HookServiceMethod( +// nameof(IAsyncClientScriptService.GetStatusAsync), +// async (_, request) => +// { +// await Task.CompletedTask; +// +// cts.Cancel(); +// scriptStatusRequest = request; +// }) +// .HookServiceMethod( +// nameof(IAsyncClientScriptService.CancelScriptAsync), +// async (_, _) => +// { +// await Task.CompletedTask; +// +// if (recordedUsages.For(nameof(IAsyncClientScriptService.CancelScriptAsync)).LastException == null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// }) +// .Build()) +// .Build(CancellationToken); +// +// var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("hello") +// .WaitForFileToExist(waitForFile) +// .Print("AllDone")) +// .Build(); +// +// var logs = new List(); +// +// var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, cts.Token); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// var allLogs = logs.JoinLogs(); +// allLogs.Should().NotContain("AllDone"); +// recordedUsages.For(nameof(IAsyncClientScriptService.CancelScriptAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientScriptService.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptService.CancelScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptService.CompleteScriptAsync)).Started.Should().Be(0); +// +// // Let the script finish. +// File.WriteAllText(waitForFile, ""); +// var legacyTentacleClient = clientTentacle.LegacyTentacleClientBuilder().Build(); +// +// await Wait.For( +// async () => (await legacyTentacleClient.ScriptService.GetStatusAsync(scriptStatusRequest, new(CancellationToken))).State == ProcessState.Complete, +// TimeSpan.FromSeconds(60), +// () => throw new Exception("Script Execution did not complete"), +// CancellationToken); +// } +// +// [Test] +// [TentacleConfigurations(testNoCapabilitiesServiceVersions: true)] +// public async Task WhenANetworkFailureOccurs_DuringCompleteScript_WithATentacleThatOnlySupportsV1ScriptService_TheCallIsNotRetried(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithPortForwarderDataLogging() +// .WithResponseMessageTcpKiller(out var responseMessageTcpKiller) +// .WithRetryDuration(TimeSpan.FromMinutes(4)) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .HookServiceMethod( +// nameof(IAsyncClientScriptService.CompleteScriptAsync), +// async (_, _) => +// { +// await Task.CompletedTask; +// +// if (recordedUsages.For(nameof(IAsyncClientScriptService.CompleteScriptAsync)).LastException == null) +// { +// responseMessageTcpKiller.KillConnectionOnNextResponse(); +// } +// }) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder().SetScriptBody(new ScriptBuilder().Print("hello")).Build(); +// +// var logs = new List(); +// var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); +// +// var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ConnectionFaulted, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); +// +// await AssertionExtensions.Should(async () => await executeScriptTask).ThrowExceptionContractAsync(expectedException); +// +// // We Can not verify what will be in the logs because of race conditions in tentacle. +// // The last complete script which we fail might come back with the logs. +// recordedUsages.For(nameof(IAsyncClientScriptService.CompleteScriptAsync)).LastException.Should().NotBeNull(); +// recordedUsages.For(nameof(IAsyncClientScriptService.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptService.CompleteScriptAsync)).Started.Should().Be(1); +// } +// } +// } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionWorksWithMultipleVersions.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionWorksWithMultipleVersions.cs index a28ed3bde..e10dcbfa4 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionWorksWithMultipleVersions.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionWorksWithMultipleVersions.cs @@ -1,39 +1,39 @@ -using System; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils.Builders; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ClientScriptExecutionWorksWithMultipleVersions : IntegrationTest - { - [Test] - [TentacleConfigurations(testCommonVersions: true)] - public async Task CanRunScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder().Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("hello") - .PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1)) - .Print("AllDone")) - .Build(); - - var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - - finalResponse.State.Should().Be(ProcessState.Complete); - finalResponse.ExitCode.Should().Be(0); - - var allLogs = logs.JoinLogs(); - - allLogs.Should().MatchRegex(".*hello\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nAllDone"); - } - } -} +// using System; +// using System.Threading.Tasks; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils.Builders; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ClientScriptExecutionWorksWithMultipleVersions : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(testCommonVersions: true)] +// public async Task CanRunScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder().Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("hello") +// .PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1)) +// .Print("AllDone")) +// .Build(); +// +// var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// +// finalResponse.State.Should().Be(ProcessState.Complete); +// finalResponse.ExitCode.Should().Be(0); +// +// var allLogs = logs.JoinLogs(); +// +// allLogs.Should().MatchRegex(".*hello\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nAllDone"); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutorObservesScriptObserverBackoffStrategy.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutorObservesScriptObserverBackoffStrategy.cs index b61e5aeb0..7e64a5483 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutorObservesScriptObserverBackoffStrategy.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutorObservesScriptObserverBackoffStrategy.cs @@ -1,42 +1,42 @@ -using System; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils.Builders; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ClientScriptExecutorObservesScriptObserverBackoffStrategy : IntegrationTest - { - [Test] - [TentacleConfigurations(testCommonVersions: true)] - public async Task TheScriptObserverBackoffShouldBeRespected(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithScriptObserverBackoffStrategy(new FuncScriptObserverBackoffStrategy(iters => TimeSpan.FromSeconds(20))) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder().PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1))) - .Build(); - - var (_, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started - .Should() - .BeGreaterThan(0) - .And - .BeLessThan(3); - } - } -} +// using System; +// using System.Threading.Tasks; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils.Builders; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ClientScriptExecutorObservesScriptObserverBackoffStrategy : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(testCommonVersions: true)] +// public async Task TheScriptObserverBackoffShouldBeRespected(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithScriptObserverBackoffStrategy(new FuncScriptObserverBackoffStrategy(iters => TimeSpan.FromSeconds(20))) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder().PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1))) +// .Build(); +// +// var (_, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started +// .Should() +// .BeGreaterThan(0) +// .And +// .BeLessThan(3); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/FileTransferServiceTests.cs b/source/Octopus.Tentacle.Tests.Integration/FileTransferServiceTests.cs index bce59c8cc..191895204 100644 --- a/source/Octopus.Tentacle.Tests.Integration/FileTransferServiceTests.cs +++ b/source/Octopus.Tentacle.Tests.Integration/FileTransferServiceTests.cs @@ -1,61 +1,61 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class FileTransferServiceTests : IntegrationTest - { - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task UploadFileSuccessfully(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - using var fileToUpload = new RandomTemporaryFileBuilder().Build(); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateLegacyBuilder().Build(CancellationToken); - - var dataStream = new DataStream( - fileToUpload.File.Length, - async (stream, ct) => - { - using var fileStream = File.OpenRead(fileToUpload.File.FullName); - await fileStream.CopyToAsync(stream); - }); - - var uploadResult = await clientAndTentacle.TentacleClient.FileTransferService.UploadFileAsync("the_remote_uploaded_file", dataStream, new(CancellationToken)); - - Console.WriteLine($"Source: {fileToUpload.File.FullName}"); - Console.WriteLine($"Destination: {uploadResult.FullPath}"); - - var sourceBytes = File.ReadAllBytes(fileToUpload.File.FullName); - var destinationBytes = File.ReadAllBytes(uploadResult.FullPath); - - sourceBytes.Should().BeEquivalentTo(destinationBytes); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task DownloadFileSuccessfully(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - using var fileToDownload = new RandomTemporaryFileBuilder().Build(); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateLegacyBuilder().Build(CancellationToken); - - var downloadedData = await clientAndTentacle.TentacleClient.FileTransferService.DownloadFileAsync( - fileToDownload.File.FullName, - new(CancellationToken)); - - var sourceBytes = File.ReadAllBytes(fileToDownload.File.FullName); - var destinationBytes = await downloadedData.ToBytes(CancellationToken); - - destinationBytes.Should().BeEquivalentTo(sourceBytes); - } - } -} +// using System; +// using System.IO; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class FileTransferServiceTests : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task UploadFileSuccessfully(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// using var fileToUpload = new RandomTemporaryFileBuilder().Build(); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateLegacyBuilder().Build(CancellationToken); +// +// var dataStream = new DataStream( +// fileToUpload.File.Length, +// async (stream, ct) => +// { +// using var fileStream = File.OpenRead(fileToUpload.File.FullName); +// await fileStream.CopyToAsync(stream); +// }); +// +// var uploadResult = await clientAndTentacle.TentacleClient.FileTransferService.UploadFileAsync("the_remote_uploaded_file", dataStream, new(CancellationToken)); +// +// Console.WriteLine($"Source: {fileToUpload.File.FullName}"); +// Console.WriteLine($"Destination: {uploadResult.FullPath}"); +// +// var sourceBytes = File.ReadAllBytes(fileToUpload.File.FullName); +// var destinationBytes = File.ReadAllBytes(uploadResult.FullPath); +// +// sourceBytes.Should().BeEquivalentTo(destinationBytes); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task DownloadFileSuccessfully(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// using var fileToDownload = new RandomTemporaryFileBuilder().Build(); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateLegacyBuilder().Build(CancellationToken); +// +// var downloadedData = await clientAndTentacle.TentacleClient.FileTransferService.DownloadFileAsync( +// fileToDownload.File.FullName, +// new(CancellationToken)); +// +// var sourceBytes = File.ReadAllBytes(fileToDownload.File.FullName); +// var destinationBytes = await downloadedData.ToBytes(CancellationToken); +// +// destinationBytes.Should().BeEquivalentTo(sourceBytes); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/MachineConfigurationHomeDirectoryTests.cs b/source/Octopus.Tentacle.Tests.Integration/MachineConfigurationHomeDirectoryTests.cs index aabcde625..b0d1558be 100644 --- a/source/Octopus.Tentacle.Tests.Integration/MachineConfigurationHomeDirectoryTests.cs +++ b/source/Octopus.Tentacle.Tests.Integration/MachineConfigurationHomeDirectoryTests.cs @@ -1,54 +1,54 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Variables; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class MachineConfigurationHomeDirectoryTests : IntegrationTest - { - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task ShouldUseTheDefaultMachineConfigurationHomeDirectoryWhenACustomLocationIsNotProvided(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using (var clientAndTentacle = await tentacleConfigurationTestCase - .CreateBuilder() - .UseDefaultMachineConfigurationHomeDirectory() - .Build(CancellationToken)) - { - - var defaultMachineConfigurationHomeDirectory = PlatformDetection.IsRunningOnWindows ? - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Octopus", "Tentacle", "Instances") : - "/etc/octopus/Tentacle/Instances"; - - var expectedInstanceConfigurationFilePath = new FileInfo(Path.Combine(defaultMachineConfigurationHomeDirectory, $"{clientAndTentacle.RunningTentacle.InstanceName}.config")); - - expectedInstanceConfigurationFilePath.Exists.Should().BeTrue($"Instance configuration file should exist {expectedInstanceConfigurationFilePath.FullName}"); - } - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task ShouldUseTheCustomMachineConfigurationHomeDirectoryWhenACustomLocationIsProvided(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - using var tempDirectory = new TemporaryDirectory(); - await using var clientAndTentacle = await tentacleConfigurationTestCase - .CreateBuilder() - .UseDefaultMachineConfigurationHomeDirectory() - .WithTentacle(x => - { - x.WithRunTentacleEnvironmentVariable(EnvironmentVariables.TentacleMachineConfigurationHomeDirectory, tempDirectory.DirectoryPath); - }) - .Build(CancellationToken); - - var expectedInstanceConfigurationFilePath = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "Tentacle", "Instances", $"{clientAndTentacle.RunningTentacle.InstanceName}.config")); - - expectedInstanceConfigurationFilePath.Exists.Should().BeTrue($"Instance configuration file should exist {expectedInstanceConfigurationFilePath.FullName}"); - } - } -} +// using System; +// using System.IO; +// using System.Threading.Tasks; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Variables; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class MachineConfigurationHomeDirectoryTests : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task ShouldUseTheDefaultMachineConfigurationHomeDirectoryWhenACustomLocationIsNotProvided(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using (var clientAndTentacle = await tentacleConfigurationTestCase +// .CreateBuilder() +// .UseDefaultMachineConfigurationHomeDirectory() +// .Build(CancellationToken)) +// { +// +// var defaultMachineConfigurationHomeDirectory = PlatformDetection.IsRunningOnWindows ? +// Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Octopus", "Tentacle", "Instances") : +// "/etc/octopus/Tentacle/Instances"; +// +// var expectedInstanceConfigurationFilePath = new FileInfo(Path.Combine(defaultMachineConfigurationHomeDirectory, $"{clientAndTentacle.RunningTentacle.InstanceName}.config")); +// +// expectedInstanceConfigurationFilePath.Exists.Should().BeTrue($"Instance configuration file should exist {expectedInstanceConfigurationFilePath.FullName}"); +// } +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task ShouldUseTheCustomMachineConfigurationHomeDirectoryWhenACustomLocationIsProvided(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// using var tempDirectory = new TemporaryDirectory(); +// await using var clientAndTentacle = await tentacleConfigurationTestCase +// .CreateBuilder() +// .UseDefaultMachineConfigurationHomeDirectory() +// .WithTentacle(x => +// { +// x.WithRunTentacleEnvironmentVariable(EnvironmentVariables.TentacleMachineConfigurationHomeDirectory, tempDirectory.DirectoryPath); +// }) +// .Build(CancellationToken); +// +// var expectedInstanceConfigurationFilePath = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "Tentacle", "Instances", $"{clientAndTentacle.RunningTentacle.InstanceName}.config")); +// +// expectedInstanceConfigurationFilePath.Exists.Should().BeTrue($"Instance configuration file should exist {expectedInstanceConfigurationFilePath.FullName}"); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/ScriptServiceTests.cs b/source/Octopus.Tentacle.Tests.Integration/ScriptServiceTests.cs index b855caeb9..7d1847411 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ScriptServiceTests.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ScriptServiceTests.cs @@ -1,136 +1,136 @@ -using System; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut.Logging; -using NUnit.Framework; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Support.Legacy; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ScriptServiceTests : IntegrationTest - { - [Test] - [TentacleConfigurations] - public async Task RunScriptWithSuccess(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var windowsScript = @" - Write-Host ""This is the start of the script"" - Write-Host ""The answer is"" (6 * 7) - Start-Sleep -Seconds 3 - Write-Host ""This is the end of the script"""; - - var nixScript = @" - echo This is the start of the script - val=6 - ((theAnswer=$val*7)) - echo The answer is $theAnswer - sleep 3 - echo This is the end of the script"; - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateLegacyBuilder().Build(CancellationToken); - - var scriptStatusResponse = await new ScriptExecutionOrchestrator(clientAndTentacle.TentacleClient, Logger) - .ExecuteScript(windowsScript, nixScript, CancellationToken); - - DumpLog(scriptStatusResponse); - - scriptStatusResponse.State.Should().Be(ProcessState.Complete); - scriptStatusResponse.ExitCode.Should().Be(0); - scriptStatusResponse.Logs.Select(x => x.Text).Should().Contain("The answer is 42"); - } - - [Test] - [TentacleConfigurations] - public async Task RunScriptWithErrors(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var windowsScript = @" - Write-Host ""This is the start of the script"" - Start-Sleep -Seconds 3 - throw ""Whoopsy Daisy!"" - Write-Host ""This is the end of the script"""; - - var nixScript = @" - echo This is the start of the script - sleep 3 - echo ""Whoopsy Daisy!"" - exit 1 - echo This is the end of the script"; - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateLegacyBuilder().Build(CancellationToken); - - var scriptStatusResponse = await new ScriptExecutionOrchestrator(clientAndTentacle.TentacleClient, Logger) - .ExecuteScript(windowsScript, nixScript, CancellationToken); - - DumpLog(scriptStatusResponse); - - scriptStatusResponse.State.Should().Be(ProcessState.Complete); - scriptStatusResponse.ExitCode.Should().NotBe(0); - scriptStatusResponse.Logs.Select(x => x.Text).Should().Contain("Whoopsy Daisy!"); - scriptStatusResponse.Logs.Select(x => x.Text).Should().NotContain("This is the end of the script"); - } - - [Test] - [TentacleConfigurations] - public async Task CancelScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var windowsScript = @"Write-Host ""This is the start of the script"" - & ping.exe 127.0.0.1 -n 100 - Write-Host ""This is the end of the script"""; - - var nixScript = @"echo This is the start of the script - ping 127.0.0.1 -c 100 - echo This is the end of the script"; - - await using var clientAndTentacle = await tentacleConfigurationTestCase - .CreateLegacyBuilder() - .WithHalibutLoggingLevel(LogLevel.Trace) - .Build(CancellationToken); - - var scriptExecutor = new ScriptExecutionOrchestrator(clientAndTentacle.TentacleClient, Logger); - - Logger.Information("Starting script execution"); - var ticket = await scriptExecutor.StartScript(windowsScript, nixScript, CancellationToken); - - // Possible Tentacle BUG: If we just observe until the first output is received then sometimes the script will fail to Cancel - await scriptExecutor.ObserverUntilScriptOutputReceived(ticket, "This is the start of the script", CancellationToken); - - Logger.Information("Cancelling script execution"); - await clientAndTentacle.TentacleClient.ScriptService.CancelScriptAsync(new CancelScriptCommand(ticket, 0), new(CancellationToken)); - - var cancellationDuration = Stopwatch.StartNew(); - - Logger.Information("Waiting for Script Execution to complete"); - var finalScriptStatusResponse = await scriptExecutor.ObserverUntilComplete(ticket, CancellationToken); - cancellationDuration.Stop(); - - Logger.Information("Completing script execution"); - var finalStatus = await scriptExecutor.CompleteScript(finalScriptStatusResponse, CancellationToken); - - DumpLog(finalStatus); - - finalStatus.State.Should().Be(ProcessState.Complete); - finalStatus.ExitCode.Should().NotBe(0, "Expected ExitCode to be non-zero"); - finalStatus.Logs.Count.Should().BeGreaterThan(0, "Expected something in the logs"); - - finalStatus.Logs.Select(x => x.Text).Should().Contain("This is the start of the script"); - finalStatus.Logs.Select(x => x.Text).Should().NotContain("This is the end of the script"); - cancellationDuration.Elapsed.TotalSeconds.Should().BeLessThanOrEqualTo(20); - } - - private static void DumpLog(ScriptStatusResponse finalStatus) - { - Console.WriteLine("### Start of script result logs ###"); - foreach (var log in finalStatus.Logs) - { - Console.WriteLine(log.Text); - } - - Console.WriteLine("### End of script result logs ###"); - } - } -} +// using System; +// using System.Diagnostics; +// using System.Linq; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut.Logging; +// using NUnit.Framework; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Support.Legacy; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ScriptServiceTests : IntegrationTest +// { +// [Test] +// [TentacleConfigurations] +// public async Task RunScriptWithSuccess(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var windowsScript = @" +// Write-Host ""This is the start of the script"" +// Write-Host ""The answer is"" (6 * 7) +// Start-Sleep -Seconds 3 +// Write-Host ""This is the end of the script"""; +// +// var nixScript = @" +// echo This is the start of the script +// val=6 +// ((theAnswer=$val*7)) +// echo The answer is $theAnswer +// sleep 3 +// echo This is the end of the script"; +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateLegacyBuilder().Build(CancellationToken); +// +// var scriptStatusResponse = await new ScriptExecutionOrchestrator(clientAndTentacle.TentacleClient, Logger) +// .ExecuteScript(windowsScript, nixScript, CancellationToken); +// +// DumpLog(scriptStatusResponse); +// +// scriptStatusResponse.State.Should().Be(ProcessState.Complete); +// scriptStatusResponse.ExitCode.Should().Be(0); +// scriptStatusResponse.Logs.Select(x => x.Text).Should().Contain("The answer is 42"); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task RunScriptWithErrors(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var windowsScript = @" +// Write-Host ""This is the start of the script"" +// Start-Sleep -Seconds 3 +// throw ""Whoopsy Daisy!"" +// Write-Host ""This is the end of the script"""; +// +// var nixScript = @" +// echo This is the start of the script +// sleep 3 +// echo ""Whoopsy Daisy!"" +// exit 1 +// echo This is the end of the script"; +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateLegacyBuilder().Build(CancellationToken); +// +// var scriptStatusResponse = await new ScriptExecutionOrchestrator(clientAndTentacle.TentacleClient, Logger) +// .ExecuteScript(windowsScript, nixScript, CancellationToken); +// +// DumpLog(scriptStatusResponse); +// +// scriptStatusResponse.State.Should().Be(ProcessState.Complete); +// scriptStatusResponse.ExitCode.Should().NotBe(0); +// scriptStatusResponse.Logs.Select(x => x.Text).Should().Contain("Whoopsy Daisy!"); +// scriptStatusResponse.Logs.Select(x => x.Text).Should().NotContain("This is the end of the script"); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task CancelScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var windowsScript = @"Write-Host ""This is the start of the script"" +// & ping.exe 127.0.0.1 -n 100 +// Write-Host ""This is the end of the script"""; +// +// var nixScript = @"echo This is the start of the script +// ping 127.0.0.1 -c 100 +// echo This is the end of the script"; +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase +// .CreateLegacyBuilder() +// .WithHalibutLoggingLevel(LogLevel.Trace) +// .Build(CancellationToken); +// +// var scriptExecutor = new ScriptExecutionOrchestrator(clientAndTentacle.TentacleClient, Logger); +// +// Logger.Information("Starting script execution"); +// var ticket = await scriptExecutor.StartScript(windowsScript, nixScript, CancellationToken); +// +// // Possible Tentacle BUG: If we just observe until the first output is received then sometimes the script will fail to Cancel +// await scriptExecutor.ObserverUntilScriptOutputReceived(ticket, "This is the start of the script", CancellationToken); +// +// Logger.Information("Cancelling script execution"); +// await clientAndTentacle.TentacleClient.ScriptService.CancelScriptAsync(new CancelScriptCommand(ticket, 0), new(CancellationToken)); +// +// var cancellationDuration = Stopwatch.StartNew(); +// +// Logger.Information("Waiting for Script Execution to complete"); +// var finalScriptStatusResponse = await scriptExecutor.ObserverUntilComplete(ticket, CancellationToken); +// cancellationDuration.Stop(); +// +// Logger.Information("Completing script execution"); +// var finalStatus = await scriptExecutor.CompleteScript(finalScriptStatusResponse, CancellationToken); +// +// DumpLog(finalStatus); +// +// finalStatus.State.Should().Be(ProcessState.Complete); +// finalStatus.ExitCode.Should().NotBe(0, "Expected ExitCode to be non-zero"); +// finalStatus.Logs.Count.Should().BeGreaterThan(0, "Expected something in the logs"); +// +// finalStatus.Logs.Select(x => x.Text).Should().Contain("This is the start of the script"); +// finalStatus.Logs.Select(x => x.Text).Should().NotContain("This is the end of the script"); +// cancellationDuration.Elapsed.TotalSeconds.Should().BeLessThanOrEqualTo(20); +// } +// +// private static void DumpLog(ScriptStatusResponse finalStatus) +// { +// Console.WriteLine("### Start of script result logs ###"); +// foreach (var log in finalStatus.Logs) +// { +// Console.WriteLine(log.Text); +// } +// +// Console.WriteLine("### End of script result logs ###"); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/ScriptServiceV2IntegrationTest.cs b/source/Octopus.Tentacle.Tests.Integration/ScriptServiceV2IntegrationTest.cs index 4fb62eb72..8188d6496 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ScriptServiceV2IntegrationTest.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ScriptServiceV2IntegrationTest.cs @@ -1,214 +1,214 @@ -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils.Builders; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class ScriptServiceV2IntegrationTest : IntegrationTest - { - [Test] - [TentacleConfigurations] - public async Task CanRunScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var methodUsages) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("Lets do it") - .PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1)) - .Print("All done")) - .Build(); - - var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - - finalResponse.State.Should().Be(ProcessState.Complete); - finalResponse.ExitCode.Should().Be(0); - - var allLogs = logs.JoinLogs(); - - allLogs.Should().MatchRegex(".*Lets do it\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nAll done.*"); - - methodUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - methodUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().BeGreaterThan(2).And.BeLessThan(30); - methodUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); - methodUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); - } - - [Test] - [TentacleConfigurations] - public async Task DelayInStartScriptSavesNetworkCalls(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("Lets do it") - .PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1)) - .Print("All done")) - .WithDurationStartScriptCanWaitForScriptToFinish(TimeSpan.FromMinutes(1)) - .Build(); - - var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - - finalResponse.State.Should().Be(ProcessState.Complete); - finalResponse.ExitCode.Should().Be(0); - - var allLogs = logs.JoinLogs(); - - allLogs.Should().MatchRegex(".*Lets do it\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nAll done.*"); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(0, "Since start script should wait for the script to finish so we don't need to call get status"); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); - } - - [Test] - [TentacleConfigurations] - public async Task WhenTentacleRestartsWhileRunningAScript_TheExitCodeShouldBe_UnknownResultExitCode(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("hello") - .Sleep(TimeSpan.FromSeconds(1)) - .Print("waitingtobestopped") - .Sleep(TimeSpan.FromSeconds(100))) - .Build(); - - var semaphoreSlim = new SemaphoreSlim(0, 1); - - var executingScript = Task.Run(async () => - await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, onScriptStatusResponseReceived => - { - if (onScriptStatusResponseReceived.Logs.JoinLogs().Contains("waitingtobestopped")) - { - semaphoreSlim.Release(); - } - })); - - await semaphoreSlim.WaitAsync(CancellationToken); - - Logger.Information("Stopping and starting tentacle now."); - await clientTentacle.RunningTentacle.Restart(CancellationToken); - - var (finalResponse, logs) = await executingScript; - - finalResponse.Should().NotBeNull(); - logs.JoinLogs().Should().Contain("waitingtobestopped"); - finalResponse.State.Should().Be(ProcessState.Complete); // This is technically a lie, the process is still running on linux - finalResponse.ExitCode.Should().Be(ScriptExitCodes.UnknownResultExitCode); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().BeGreaterThan(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); - } - - [Test] - [TentacleConfigurations] - public async Task WhenALongRunningScriptIsCancelled_TheScriptShouldStop(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(out var recordedUsages) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder() - .Print("hello") - .Sleep(TimeSpan.FromSeconds(1)) - .Print("waitingtobestopped") - .Sleep(TimeSpan.FromSeconds(100))) - .Build(); - - var scriptCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); - var stopWatch = Stopwatch.StartNew(); - Exception? actualException = null; - - try - { - await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, scriptCancellationTokenSource.Token, onScriptStatusResponseReceived => - { - if (onScriptStatusResponseReceived.Logs.JoinLogs().Contains("waitingtobestopped")) - { - scriptCancellationTokenSource.Cancel(); - } - }); - } - catch (Exception ex) - { - actualException = ex; - } - - stopWatch.Stop(); - - actualException.Should().NotBeNull().And.BeOfType().And.Match(x => x.Message == "Script execution was cancelled"); - stopWatch.Elapsed.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(10)); - - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().BeGreaterThan(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().BeGreaterThanOrEqualTo(1); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.Version2)] - public async Task WhenOnCompleteTakesLongerThan_OnCancellationAbandonCompleteScriptAfter_AndTheExecutionIsNotCancelled_TheOrchestratorWaitsForCleanUpToComplete(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - bool calledWithNonCancelledCT = false; - - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .DecorateScriptServiceV2With(b => b - .BeforeCompleteScript(async (_, _, halibutProxyRequestOptions) => - { - await Task.Delay(TimeSpan.FromSeconds(2), CancellationToken); - calledWithNonCancelledCT = !halibutProxyRequestOptions.RequestCancellationToken.IsCancellationRequested; - }) - .Build()) - .Build()) - .Build(CancellationToken); - - var scriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b.Print("Hello")) - .Build(); - - var tentacleClient = clientTentacle.TentacleClient; - - tentacleClient.OnCancellationAbandonCompleteScriptAfter = TimeSpan.FromMilliseconds(1); - - await tentacleClient.ExecuteScript(scriptCommand, CancellationToken); - - - calledWithNonCancelledCT.Should().Be(true); - } - } -} +// using System; +// using System.Diagnostics; +// using System.Threading; +// using System.Threading.Tasks; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils.Builders; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class ScriptServiceV2IntegrationTest : IntegrationTest +// { +// [Test] +// [TentacleConfigurations] +// public async Task CanRunScript(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var methodUsages) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("Lets do it") +// .PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1)) +// .Print("All done")) +// .Build(); +// +// var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// +// finalResponse.State.Should().Be(ProcessState.Complete); +// finalResponse.ExitCode.Should().Be(0); +// +// var allLogs = logs.JoinLogs(); +// +// allLogs.Should().MatchRegex(".*Lets do it\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nAll done.*"); +// +// methodUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// methodUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().BeGreaterThan(2).And.BeLessThan(30); +// methodUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); +// methodUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task DelayInStartScriptSavesNetworkCalls(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("Lets do it") +// .PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1)) +// .Print("All done")) +// .WithDurationStartScriptCanWaitForScriptToFinish(TimeSpan.FromMinutes(1)) +// .Build(); +// +// var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// +// finalResponse.State.Should().Be(ProcessState.Complete); +// finalResponse.ExitCode.Should().Be(0); +// +// var allLogs = logs.JoinLogs(); +// +// allLogs.Should().MatchRegex(".*Lets do it\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nanother one\nAll done.*"); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().Be(0, "Since start script should wait for the script to finish so we don't need to call get status"); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenTentacleRestartsWhileRunningAScript_TheExitCodeShouldBe_UnknownResultExitCode(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("hello") +// .Sleep(TimeSpan.FromSeconds(1)) +// .Print("waitingtobestopped") +// .Sleep(TimeSpan.FromSeconds(100))) +// .Build(); +// +// var semaphoreSlim = new SemaphoreSlim(0, 1); +// +// var executingScript = Task.Run(async () => +// await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, onScriptStatusResponseReceived => +// { +// if (onScriptStatusResponseReceived.Logs.JoinLogs().Contains("waitingtobestopped")) +// { +// semaphoreSlim.Release(); +// } +// })); +// +// await semaphoreSlim.WaitAsync(CancellationToken); +// +// Logger.Information("Stopping and starting tentacle now."); +// await clientTentacle.RunningTentacle.Restart(CancellationToken); +// +// var (finalResponse, logs) = await executingScript; +// +// finalResponse.Should().NotBeNull(); +// logs.JoinLogs().Should().Contain("waitingtobestopped"); +// finalResponse.State.Should().Be(ProcessState.Complete); // This is technically a lie, the process is still running on linux +// finalResponse.ExitCode.Should().Be(ScriptExitCodes.UnknownResultExitCode); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().BeGreaterThan(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().Be(0); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task WhenALongRunningScriptIsCancelled_TheScriptShouldStop(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(out var recordedUsages) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder() +// .Print("hello") +// .Sleep(TimeSpan.FromSeconds(1)) +// .Print("waitingtobestopped") +// .Sleep(TimeSpan.FromSeconds(100))) +// .Build(); +// +// var scriptCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); +// var stopWatch = Stopwatch.StartNew(); +// Exception? actualException = null; +// +// try +// { +// await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, scriptCancellationTokenSource.Token, onScriptStatusResponseReceived => +// { +// if (onScriptStatusResponseReceived.Logs.JoinLogs().Contains("waitingtobestopped")) +// { +// scriptCancellationTokenSource.Cancel(); +// } +// }); +// } +// catch (Exception ex) +// { +// actualException = ex; +// } +// +// stopWatch.Stop(); +// +// actualException.Should().NotBeNull().And.BeOfType().And.Match(x => x.Message == "Script execution was cancelled"); +// stopWatch.Elapsed.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(10)); +// +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).Started.Should().BeGreaterThan(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().BeGreaterThanOrEqualTo(1); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.Version2)] +// public async Task WhenOnCompleteTakesLongerThan_OnCancellationAbandonCompleteScriptAfter_AndTheExecutionIsNotCancelled_TheOrchestratorWaitsForCleanUpToComplete(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// bool calledWithNonCancelledCT = false; +// +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .DecorateScriptServiceV2With(b => b +// .BeforeCompleteScript(async (_, _, halibutProxyRequestOptions) => +// { +// await Task.Delay(TimeSpan.FromSeconds(2), CancellationToken); +// calledWithNonCancelledCT = !halibutProxyRequestOptions.RequestCancellationToken.IsCancellationRequested; +// }) +// .Build()) +// .Build()) +// .Build(CancellationToken); +// +// var scriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b.Print("Hello")) +// .Build(); +// +// var tentacleClient = clientTentacle.TentacleClient; +// +// tentacleClient.OnCancellationAbandonCompleteScriptAfter = TimeSpan.FromMilliseconds(1); +// +// await tentacleClient.ExecuteScript(scriptCommand, CancellationToken); +// +// +// calledWithNonCancelledCT.Should().Be(true); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/Startup/LinuxConfigureServiceHelperFixture.cs b/source/Octopus.Tentacle.Tests.Integration/Startup/LinuxConfigureServiceHelperFixture.cs index bb8e45aef..da78bc322 100644 --- a/source/Octopus.Tentacle.Tests.Integration/Startup/LinuxConfigureServiceHelperFixture.cs +++ b/source/Octopus.Tentacle.Tests.Integration/Startup/LinuxConfigureServiceHelperFixture.cs @@ -1,187 +1,187 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils.Diagnostics; -using Octopus.Tentacle.Startup; -using Octopus.Tentacle.Tests.Integration.Support.TestAttributes; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Util; - -namespace Octopus.Tentacle.Tests.Integration.Startup -{ - [TestFixture] - [LinuxTest] - [NonParallelizable] - public class LinuxConfigureServiceHelperFixture - { - [Test] - [RequiresSudoOnLinux] - public void CanInstallServiceAsRoot() - { - CanInstallService(null, null); - } - - [Test] - [RequiresSudoOnLinux] - public void CanInstallServiceAsUser() - { - var user = new LinuxTestUserPrincipal("octo-shared-svc-test"); - CanInstallService(user.UserName, user.Password); - } - - [Test] - [RequiresSudoOnLinux] - public void CannotWriteToServiceFileAsUser() - { - const string serviceName = "OctopusShared.ServiceHelperTest"; - const string instance = "TestCannotWriteToServiceFileInstance"; - const string serviceDescription = "Test service for OctopusShared tests"; - var log = new InMemoryLog(); - var root = Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullProcessPath()); - var scriptPath = Path.Combine(root, "SampleScript.sh"); - WriteUnixFile(scriptPath); - - var chmodCmd = new CommandLineInvocation("/bin/bash", $"-c \"chmod 777 {scriptPath}\""); - chmodCmd.ExecuteCommand(); - - var configureServiceHelper = new LinuxServiceConfigurator(log); - - var serviceConfigurationState = new ServiceConfigurationState - { - Install = true, - Start = true, - Username = "user", - Password = "password" - }; - - configureServiceHelper.ConfigureServiceByInstanceName(serviceName, - scriptPath, - instance, - serviceDescription, - serviceConfigurationState); - - var statCmd = new CommandLineInvocation("/bin/bash", $"-c \"stat -c '%A' /etc/systemd/system/{instance}.service\""); - var result = statCmd.ExecuteCommand(); - result.Infos.Single().Should().Be("-rw-r--r--"); // Service file should only be writeable for the root user - } - - void CanInstallService(string username, string password) - { - const string serviceName = "OctopusShared.ServiceHelperTest"; - const string instance = "TestInstance"; - const string serviceDescription = "Test service for OctopusShared tests"; - var log = new InMemoryLog(); - var root = Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullProcessPath()); - var scriptPath = Path.Combine(root, "SampleScript.sh"); - WriteUnixFile(scriptPath); - - var commandLineInvocation = new CommandLineInvocation("/bin/bash", $"-c \"chmod 777 {scriptPath}\""); - commandLineInvocation.ExecuteCommand(); - - var configureServiceHelper = new LinuxServiceConfigurator(log); - - var serviceConfigurationState = new ServiceConfigurationState - { - Install = true, - Start = true, - Username = username, - Password = password - }; - - configureServiceHelper.ConfigureServiceByInstanceName(serviceName, - scriptPath, - instance, - serviceDescription, - serviceConfigurationState); - - //Check that the systemd unit service file has been written - Assert.IsTrue(DoesServiceUnitFileExist(instance), "The service unit file has not been created"); - - var status = GetServiceStatus(instance); - status["ActiveState"].Should().Be("active"); - status["SubState"].Should().Be("running"); - status["LoadState"].Should().Be("loaded"); - status["User"].Should().Be(username ?? "root"); - - //Check that the Service is running - Assert.IsTrue(IsServiceRunning(instance), "The service is not running"); - - //Check that the service is enabled to run on startup - Assert.IsTrue(IsServiceEnabled(instance), "The service has not been enabled to run on startup"); - - var stopServiceConfigurationState = new ServiceConfigurationState - { - Stop = true, - Uninstall = true - }; - - configureServiceHelper.ConfigureServiceByInstanceName(serviceName, - scriptPath, - instance, - serviceDescription, - stopServiceConfigurationState); - - //Check that the Service has stopped - Assert.IsFalse(IsServiceRunning(instance), "The service has not been stopped"); - - //Check that the service is disabled - Assert.IsFalse(IsServiceEnabled(instance), "The service has not been disabled"); - - //Check that the service is disabled - Assert.IsFalse(DoesServiceUnitFileExist(instance), "The service unit file still exists"); - } - - void WriteUnixFile(string path) - { - using (TextWriter writer = new StreamWriter(path, false, Encoding.ASCII)) - { - writer.NewLine = "\n"; - writer.WriteLine("#!/bin/bash"); - writer.WriteLine(""); - writer.WriteLine("while true; do now=$(date +\"%T\");echo \"Current time : $now\";sleep 1; done\n"); - writer.Close(); - } - } - - Dictionary GetServiceStatus(string serviceName) - { - var commandLineInvocation = new CommandLineInvocation("/bin/bash", $"-c \"systemctl show {serviceName}\""); - var result = commandLineInvocation.ExecuteCommand(); - Console.WriteLine($"Status of service {serviceName}"); - foreach (var info in result.Infos) - Console.WriteLine(info); - return result.Infos - .Select(x => x.Split(new[] { '=' }, 2, StringSplitOptions.None)) - .ToDictionary(x => x[0], x => x[1]); - } - - bool IsServiceRunning(string serviceName) - { - var result = RunBashCommand($"systemctl is-active --quiet {serviceName}"); - return result.ExitCode == 0; - } - - bool IsServiceEnabled(string serviceName) - { - var result = RunBashCommand($"systemctl is-enabled --quiet {serviceName}"); - return result.ExitCode == 0; - } - - bool DoesServiceUnitFileExist(string serviceName) - { - var result = RunBashCommand($"ls /etc/systemd/system | grep {serviceName}.service"); - return result.ExitCode == 0; - } - - CmdResult RunBashCommand(string command) - { - var commandLineInvocation = new CommandLineInvocation("/bin/bash", $"-c \"{command}\""); - return commandLineInvocation.ExecuteCommand(); - } - } -} +// using System; +// using System.Collections.Generic; +// using System.IO; +// using System.Linq; +// using System.Reflection; +// using System.Text; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils.Diagnostics; +// using Octopus.Tentacle.Startup; +// using Octopus.Tentacle.Tests.Integration.Support.TestAttributes; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Util; +// +// namespace Octopus.Tentacle.Tests.Integration.Startup +// { +// [TestFixture] +// [LinuxTest] +// [NonParallelizable] +// public class LinuxConfigureServiceHelperFixture +// { +// [Test] +// [RequiresSudoOnLinux] +// public void CanInstallServiceAsRoot() +// { +// CanInstallService(null, null); +// } +// +// [Test] +// [RequiresSudoOnLinux] +// public void CanInstallServiceAsUser() +// { +// var user = new LinuxTestUserPrincipal("octo-shared-svc-test"); +// CanInstallService(user.UserName, user.Password); +// } +// +// [Test] +// [RequiresSudoOnLinux] +// public void CannotWriteToServiceFileAsUser() +// { +// const string serviceName = "OctopusShared.ServiceHelperTest"; +// const string instance = "TestCannotWriteToServiceFileInstance"; +// const string serviceDescription = "Test service for OctopusShared tests"; +// var log = new InMemoryLog(); +// var root = Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullProcessPath()); +// var scriptPath = Path.Combine(root, "SampleScript.sh"); +// WriteUnixFile(scriptPath); +// +// var chmodCmd = new CommandLineInvocation("/bin/bash", $"-c \"chmod 777 {scriptPath}\""); +// chmodCmd.ExecuteCommand(); +// +// var configureServiceHelper = new LinuxServiceConfigurator(log); +// +// var serviceConfigurationState = new ServiceConfigurationState +// { +// Install = true, +// Start = true, +// Username = "user", +// Password = "password" +// }; +// +// configureServiceHelper.ConfigureServiceByInstanceName(serviceName, +// scriptPath, +// instance, +// serviceDescription, +// serviceConfigurationState); +// +// var statCmd = new CommandLineInvocation("/bin/bash", $"-c \"stat -c '%A' /etc/systemd/system/{instance}.service\""); +// var result = statCmd.ExecuteCommand(); +// result.Infos.Single().Should().Be("-rw-r--r--"); // Service file should only be writeable for the root user +// } +// +// void CanInstallService(string username, string password) +// { +// const string serviceName = "OctopusShared.ServiceHelperTest"; +// const string instance = "TestInstance"; +// const string serviceDescription = "Test service for OctopusShared tests"; +// var log = new InMemoryLog(); +// var root = Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullProcessPath()); +// var scriptPath = Path.Combine(root, "SampleScript.sh"); +// WriteUnixFile(scriptPath); +// +// var commandLineInvocation = new CommandLineInvocation("/bin/bash", $"-c \"chmod 777 {scriptPath}\""); +// commandLineInvocation.ExecuteCommand(); +// +// var configureServiceHelper = new LinuxServiceConfigurator(log); +// +// var serviceConfigurationState = new ServiceConfigurationState +// { +// Install = true, +// Start = true, +// Username = username, +// Password = password +// }; +// +// configureServiceHelper.ConfigureServiceByInstanceName(serviceName, +// scriptPath, +// instance, +// serviceDescription, +// serviceConfigurationState); +// +// //Check that the systemd unit service file has been written +// Assert.IsTrue(DoesServiceUnitFileExist(instance), "The service unit file has not been created"); +// +// var status = GetServiceStatus(instance); +// status["ActiveState"].Should().Be("active"); +// status["SubState"].Should().Be("running"); +// status["LoadState"].Should().Be("loaded"); +// status["User"].Should().Be(username ?? "root"); +// +// //Check that the Service is running +// Assert.IsTrue(IsServiceRunning(instance), "The service is not running"); +// +// //Check that the service is enabled to run on startup +// Assert.IsTrue(IsServiceEnabled(instance), "The service has not been enabled to run on startup"); +// +// var stopServiceConfigurationState = new ServiceConfigurationState +// { +// Stop = true, +// Uninstall = true +// }; +// +// configureServiceHelper.ConfigureServiceByInstanceName(serviceName, +// scriptPath, +// instance, +// serviceDescription, +// stopServiceConfigurationState); +// +// //Check that the Service has stopped +// Assert.IsFalse(IsServiceRunning(instance), "The service has not been stopped"); +// +// //Check that the service is disabled +// Assert.IsFalse(IsServiceEnabled(instance), "The service has not been disabled"); +// +// //Check that the service is disabled +// Assert.IsFalse(DoesServiceUnitFileExist(instance), "The service unit file still exists"); +// } +// +// void WriteUnixFile(string path) +// { +// using (TextWriter writer = new StreamWriter(path, false, Encoding.ASCII)) +// { +// writer.NewLine = "\n"; +// writer.WriteLine("#!/bin/bash"); +// writer.WriteLine(""); +// writer.WriteLine("while true; do now=$(date +\"%T\");echo \"Current time : $now\";sleep 1; done\n"); +// writer.Close(); +// } +// } +// +// Dictionary GetServiceStatus(string serviceName) +// { +// var commandLineInvocation = new CommandLineInvocation("/bin/bash", $"-c \"systemctl show {serviceName}\""); +// var result = commandLineInvocation.ExecuteCommand(); +// Console.WriteLine($"Status of service {serviceName}"); +// foreach (var info in result.Infos) +// Console.WriteLine(info); +// return result.Infos +// .Select(x => x.Split(new[] { '=' }, 2, StringSplitOptions.None)) +// .ToDictionary(x => x[0], x => x[1]); +// } +// +// bool IsServiceRunning(string serviceName) +// { +// var result = RunBashCommand($"systemctl is-active --quiet {serviceName}"); +// return result.ExitCode == 0; +// } +// +// bool IsServiceEnabled(string serviceName) +// { +// var result = RunBashCommand($"systemctl is-enabled --quiet {serviceName}"); +// return result.ExitCode == 0; +// } +// +// bool DoesServiceUnitFileExist(string serviceName) +// { +// var result = RunBashCommand($"ls /etc/systemd/system | grep {serviceName}.service"); +// return result.ExitCode == 0; +// } +// +// CmdResult RunBashCommand(string command) +// { +// var commandLineInvocation = new CommandLineInvocation("/bin/bash", $"-c \"{command}\""); +// return commandLineInvocation.ExecuteCommand(); +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/Startup/WindowsServiceConfiguratorFixture.cs b/source/Octopus.Tentacle.Tests.Integration/Startup/WindowsServiceConfiguratorFixture.cs index f139dd463..9bd3df3d6 100644 --- a/source/Octopus.Tentacle.Tests.Integration/Startup/WindowsServiceConfiguratorFixture.cs +++ b/source/Octopus.Tentacle.Tests.Integration/Startup/WindowsServiceConfiguratorFixture.cs @@ -1,118 +1,118 @@ -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Reflection; -using System.ServiceProcess; -using FluentAssertions; -using NSubstitute; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils.Diagnostics; -using Octopus.Tentacle.Startup; -using Octopus.Tentacle.Tests.Integration.Support.TestAttributes; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Util; - -namespace Octopus.Tentacle.Tests.Integration.Startup -{ - [TestFixture] - [WindowsTest] - [NonParallelizable] - [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - public class WindowsServiceConfiguratorFixture - { - [Test] - [RequiresAdminOnWindows] - public void CanInstallWindowsService() - { - const string serviceName = "OctopusShared.ServiceHelperTest"; - const string instance = "TestInstance"; - const string serviceDescription = "Test service for OctopusShared tests"; - var log = new InMemoryLog(); - var root = Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullProcessPath()); - var exePath = Path.Combine(root, "Startup\\Packages\\Acme.Service", "Acme.Service.exe"); - - DeleteExistingService(serviceName); - - var user = new TestUserPrincipal("octo-shared-svc-test"); - var serviceConfigurationState = new ServiceConfigurationState - { - Install = true, - Password = user.Password, - Username = user.NTAccountName, - Start = true - }; - var localAdminRightsChecker = Substitute.For(); - var configureServiceHelper = new WindowsServiceConfigurator(log, Substitute.For(), localAdminRightsChecker); - - try - { - configureServiceHelper.ConfigureServiceByInstanceName(serviceName, - exePath, - instance, - serviceDescription, - serviceConfigurationState); - - using (var installedService = GetInstalledService(serviceName)) - { - Assert.NotNull(installedService, "Service is installed"); - Assert.AreEqual(ServiceControllerStatus.Running, installedService.Status); - } - localAdminRightsChecker.Received(1).AssertIsRunningElevated(); - } - finally - { - // Don't delete the user account - we don't delete the user profile, resulting in test failures when the profile names get too long - // Security: the user account is not a member of the local admin group, and we reset the password on every execution of the test - // user?.Delete(); - DeleteExistingService(serviceName); - } - } - - [Test] - [RequiresAdminOnWindows] - public void ThrowsOnBadServiceDependency() - { - const string serviceName = "OctopusShared.ServiceHelperTest"; - const string instance = "TestInstance"; - const string serviceDescription = "Test service for OctopusShared tests"; - var root = Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullProcessPath()); - var exePath = Path.Combine(root, "Startup\\Packages\\Acme.Service", "Acme.Service.exe"); - - DeleteExistingService(serviceName); - - var serviceConfigurationState = new ServiceConfigurationState - { - Install = false, - Start = false, - DependOn = "ServiceThatDoesNotExist" - }; - var configureServiceHelper = new WindowsServiceConfigurator(new InMemoryLog(), Substitute.For(), new WindowsLocalAdminRightsChecker()); - - var ex = Assert.Throws(() => configureServiceHelper.ConfigureServiceByInstanceName(serviceName, - exePath, - instance, - serviceDescription, - serviceConfigurationState)); - ex.Message.Should().Be("Unable to set dependency on service 'ServiceThatDoesNotExist' as no service was found with that name."); - } - ServiceController GetInstalledService(string serviceName) - { - return ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == serviceName); - } - - void DeleteExistingService(string serviceName) - { - var service = GetInstalledService(serviceName); - if (service != null) - { - var system32 = Environment.GetFolderPath(Environment.SpecialFolder.System); - var sc = Path.Combine(system32, "sc.exe"); - - Process.Start(new ProcessStartInfo(sc, $"stop {serviceName}") { CreateNoWindow = true, UseShellExecute = false })?.WaitForExit(); - Process.Start(new ProcessStartInfo(sc, $"delete {serviceName}") { CreateNoWindow = true, UseShellExecute = false })?.WaitForExit(); - } - } - } -} +// using System; +// using System.Diagnostics; +// using System.Diagnostics.CodeAnalysis; +// using System.IO; +// using System.Linq; +// using System.Reflection; +// using System.ServiceProcess; +// using FluentAssertions; +// using NSubstitute; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils.Diagnostics; +// using Octopus.Tentacle.Startup; +// using Octopus.Tentacle.Tests.Integration.Support.TestAttributes; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Util; +// +// namespace Octopus.Tentacle.Tests.Integration.Startup +// { +// [TestFixture] +// [WindowsTest] +// [NonParallelizable] +// [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] +// public class WindowsServiceConfiguratorFixture +// { +// [Test] +// [RequiresAdminOnWindows] +// public void CanInstallWindowsService() +// { +// const string serviceName = "OctopusShared.ServiceHelperTest"; +// const string instance = "TestInstance"; +// const string serviceDescription = "Test service for OctopusShared tests"; +// var log = new InMemoryLog(); +// var root = Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullProcessPath()); +// var exePath = Path.Combine(root, "Startup\\Packages\\Acme.Service", "Acme.Service.exe"); +// +// DeleteExistingService(serviceName); +// +// var user = new TestUserPrincipal("octo-shared-svc-test"); +// var serviceConfigurationState = new ServiceConfigurationState +// { +// Install = true, +// Password = user.Password, +// Username = user.NTAccountName, +// Start = true +// }; +// var localAdminRightsChecker = Substitute.For(); +// var configureServiceHelper = new WindowsServiceConfigurator(log, Substitute.For(), localAdminRightsChecker); +// +// try +// { +// configureServiceHelper.ConfigureServiceByInstanceName(serviceName, +// exePath, +// instance, +// serviceDescription, +// serviceConfigurationState); +// +// using (var installedService = GetInstalledService(serviceName)) +// { +// Assert.NotNull(installedService, "Service is installed"); +// Assert.AreEqual(ServiceControllerStatus.Running, installedService.Status); +// } +// localAdminRightsChecker.Received(1).AssertIsRunningElevated(); +// } +// finally +// { +// // Don't delete the user account - we don't delete the user profile, resulting in test failures when the profile names get too long +// // Security: the user account is not a member of the local admin group, and we reset the password on every execution of the test +// // user?.Delete(); +// DeleteExistingService(serviceName); +// } +// } +// +// [Test] +// [RequiresAdminOnWindows] +// public void ThrowsOnBadServiceDependency() +// { +// const string serviceName = "OctopusShared.ServiceHelperTest"; +// const string instance = "TestInstance"; +// const string serviceDescription = "Test service for OctopusShared tests"; +// var root = Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullProcessPath()); +// var exePath = Path.Combine(root, "Startup\\Packages\\Acme.Service", "Acme.Service.exe"); +// +// DeleteExistingService(serviceName); +// +// var serviceConfigurationState = new ServiceConfigurationState +// { +// Install = false, +// Start = false, +// DependOn = "ServiceThatDoesNotExist" +// }; +// var configureServiceHelper = new WindowsServiceConfigurator(new InMemoryLog(), Substitute.For(), new WindowsLocalAdminRightsChecker()); +// +// var ex = Assert.Throws(() => configureServiceHelper.ConfigureServiceByInstanceName(serviceName, +// exePath, +// instance, +// serviceDescription, +// serviceConfigurationState)); +// ex.Message.Should().Be("Unable to set dependency on service 'ServiceThatDoesNotExist' as no service was found with that name."); +// } +// ServiceController GetInstalledService(string serviceName) +// { +// return ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == serviceName); +// } +// +// void DeleteExistingService(string serviceName) +// { +// var service = GetInstalledService(serviceName); +// if (service != null) +// { +// var system32 = Environment.GetFolderPath(Environment.SpecialFolder.System); +// var sc = Path.Combine(system32, "sc.exe"); +// +// Process.Start(new ProcessStartInfo(sc, $"stop {serviceName}") { CreateNoWindow = true, UseShellExecute = false })?.WaitForExit(); +// Process.Start(new ProcessStartInfo(sc, $"delete {serviceName}") { CreateNoWindow = true, UseShellExecute = false })?.WaitForExit(); +// } +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/Support/ClientAndTentacle.cs b/source/Octopus.Tentacle.Tests.Integration/Support/ClientAndTentacle.cs index 9d4e9a523..09609ecbc 100644 --- a/source/Octopus.Tentacle.Tests.Integration/Support/ClientAndTentacle.cs +++ b/source/Octopus.Tentacle.Tests.Integration/Support/ClientAndTentacle.cs @@ -68,11 +68,14 @@ public async ValueTask DisposeAsync() logger.Information("Starting DisposeAsync"); - logger.Information("Starting RunningTentacle.DisposeAsync and Server.Dispose and PortForwarder.Dispose"); - var portForwarderTask = Task.Run(() => PortForwarder?.Dispose()); - var runningTentacleTask = RunningTentacle.DisposeAsync(); - var serverTask = Server.DisposeAsync(); - await Task.WhenAll(runningTentacleTask.AsTask(), serverTask.AsTask(), portForwarderTask); + logger.Information("Starting PortForwarder.Dispose"); + await Task.Run(() => PortForwarder?.Dispose()); + + logger.Information("Starting RunningTentacle.DisposeAsync"); + await RunningTentacle.DisposeAsync(); + + logger.Information("Starting Server.Dispose"); + await Server.DisposeAsync(); logger.Information("Starting TentacleClient.Dispose"); TentacleClient.Dispose(); diff --git a/source/Octopus.Tentacle.Tests.Integration/Support/IntegrationTest.cs b/source/Octopus.Tentacle.Tests.Integration/Support/IntegrationTest.cs index 79c83541d..091fe13d1 100644 --- a/source/Octopus.Tentacle.Tests.Integration/Support/IntegrationTest.cs +++ b/source/Octopus.Tentacle.Tests.Integration/Support/IntegrationTest.cs @@ -95,6 +95,7 @@ static void SafelyDeleteTempTentacleLog() } catch { + throw new Exception($"Unable to delete Tentacle Log file at {logFilePath}"); } } } @@ -106,8 +107,8 @@ public static string GetTempTentacleLogPath() public void Dispose() { - Try.CatchingError(() => cancellationTokenSource?.Cancel(), _ => {}); - Try.CatchingError(() => cancellationTokenSource?.Dispose(), _ => {}); + Try.CatchingError(() => cancellationTokenSource?.Cancel(), _ => throw new Exception("Test has failed to Cancel")); + Try.CatchingError(() => cancellationTokenSource?.Dispose(), _ => throw new Exception("Test has failed to Dispose")); } } } diff --git a/source/Octopus.Tentacle.Tests.Integration/Support/SetupFixtures/ShowOneTimeSetupLogInfo.cs b/source/Octopus.Tentacle.Tests.Integration/Support/SetupFixtures/ShowOneTimeSetupLogInfo.cs index ec957a32c..022240301 100644 --- a/source/Octopus.Tentacle.Tests.Integration/Support/SetupFixtures/ShowOneTimeSetupLogInfo.cs +++ b/source/Octopus.Tentacle.Tests.Integration/Support/SetupFixtures/ShowOneTimeSetupLogInfo.cs @@ -1,20 +1,20 @@ -using NUnit.Framework; - -namespace Octopus.Tentacle.Tests.Integration.Support.SetupFixtures -{ - public class ShowOneTimeSetupLogInfo : IntegrationTest - { - - /// - /// Teamcity doesn't seem to show these logs, it is not clear why. - /// - /// This exists to hack around that. - /// - [Test] - public void ShowOneTimeSetupLogInfoTest() - { - this.Logger.Information(TentacleIntegrationSetupFixtures.OneTimeSetupLogOutput); - TentacleIntegrationSetupFixtures.OneTimeSetupLogOutput = null; - } - } -} \ No newline at end of file +// using NUnit.Framework; +// +// namespace Octopus.Tentacle.Tests.Integration.Support.SetupFixtures +// { +// public class ShowOneTimeSetupLogInfo : IntegrationTest +// { +// +// /// +// /// Teamcity doesn't seem to show these logs, it is not clear why. +// /// +// /// This exists to hack around that. +// /// +// [Test] +// public void ShowOneTimeSetupLogInfoTest() +// { +// this.Logger.Information(TentacleIntegrationSetupFixtures.OneTimeSetupLogOutput); +// TentacleIntegrationSetupFixtures.OneTimeSetupLogOutput = null; +// } +// } +// } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/Support/TestDecoratorsAreCalledInTheCorrectOrder.cs b/source/Octopus.Tentacle.Tests.Integration/Support/TestDecoratorsAreCalledInTheCorrectOrder.cs index 09cbc58fb..f4cbdbc57 100644 --- a/source/Octopus.Tentacle.Tests.Integration/Support/TestDecoratorsAreCalledInTheCorrectOrder.cs +++ b/source/Octopus.Tentacle.Tests.Integration/Support/TestDecoratorsAreCalledInTheCorrectOrder.cs @@ -1,114 +1,114 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut.ServiceModel; -using NUnit.Framework; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Contracts.ScriptServiceV2; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators.Proxies; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; - -namespace Octopus.Tentacle.Tests.Integration.Support -{ - public class TestDecoratorsAreCalledInTheCorrectOrder - { - [Test] - public async Task MethodDecoratorsAreCalledInTheOrderTheyAreDefined() - { - var list = new List(); - var decorator = new TentacleServiceDecoratorBuilder() - .DecorateScriptServiceV2With(b => b.BeforeCompleteScript(async (_, _, _) => - { - list.Add(1); - await Task.CompletedTask; - })) - .DecorateScriptServiceV2With(b => b.BeforeCompleteScript(async (_, _, _) => - { - list.Add(2); - await Task.CompletedTask; - })) - .DecorateScriptServiceV2With(b => b.DecorateCompleteScriptWith(async (inner, command, options) => - { - await Task.CompletedTask; - await inner.CompleteScriptAsync(command, options); - list.Add(3); - })) - .DecorateScriptServiceV2With(b => b.DecorateCompleteScriptWith(async (inner, command, options) => - { - await Task.CompletedTask; - await inner.CompleteScriptAsync(command, options); - list.Add(4); - })) - .Build(); - - await decorator.Decorate(new NoOPClientScriptService()).CompleteScriptAsync(SomeCompleteScriptCommandV2(), SomeHalibutProxyRequestOptions()); - - list.Should().BeEquivalentTo(new List {1, 2, 3, 4}); - } - - [Test] - public void ProxyGeneratedDecoratorsAreFirst() - { - var list = new List(); - IRecordedMethodUsages recordedUsages = new MethodUsages(); - long startedCountInCall = 0; - var decorator = new TentacleServiceDecoratorBuilder() - .DecorateScriptServiceV2With(b => b.BeforeCompleteScript(async (_, _, _) => - { - await Task.CompletedTask; - // We should find that the proxy decorator has already counted this call. - startedCountInCall = recordedUsages.ForAll().Started; - // If the proxy decorator after this then throwing an exception here would result in calls not being counted. - throw new Exception(); - }) - .Build()) - .RecordMethodUsages(out recordedUsages) - .Build(); - - Assert.ThrowsAsync(async () => await decorator.Decorate(new NoOPClientScriptService()).CompleteScriptAsync(SomeCompleteScriptCommandV2(), SomeHalibutProxyRequestOptions())); - - var usage = recordedUsages.ForAll(); - - usage.LastException.Should().NotBeNull(); - usage.Started.Should().Be(1); - startedCountInCall.Should().Be(1, "Because the proxy decorator which count calls should happen before any method specific decorators."); - } - - static CompleteScriptCommandV2 SomeCompleteScriptCommandV2() - { - return new CompleteScriptCommandV2(new ScriptTicket("a")); - } - - static HalibutProxyRequestOptions SomeHalibutProxyRequestOptions() - { - return new HalibutProxyRequestOptions(CancellationToken.None); - } - - class NoOPClientScriptService : IAsyncClientScriptServiceV2 - { - public Task StartScriptAsync(StartScriptCommandV2 command, HalibutProxyRequestOptions proxyRequestOptions) - { - throw new NotImplementedException(); - } - - public Task GetStatusAsync(ScriptStatusRequestV2 request, HalibutProxyRequestOptions proxyRequestOptions) - { - throw new NotImplementedException(); - } - - public Task CancelScriptAsync(CancelScriptCommandV2 command, HalibutProxyRequestOptions proxyRequestOptions) - { - throw new NotImplementedException(); - } - - public async Task CompleteScriptAsync(CompleteScriptCommandV2 command, HalibutProxyRequestOptions proxyRequestOptions) - { - await Task.CompletedTask; - } - } - } -} \ No newline at end of file +// using System; +// using System.Collections.Generic; +// using System.Threading; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut.ServiceModel; +// using NUnit.Framework; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Contracts.ScriptServiceV2; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators.Proxies; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// +// namespace Octopus.Tentacle.Tests.Integration.Support +// { +// public class TestDecoratorsAreCalledInTheCorrectOrder +// { +// [Test] +// public async Task MethodDecoratorsAreCalledInTheOrderTheyAreDefined() +// { +// var list = new List(); +// var decorator = new TentacleServiceDecoratorBuilder() +// .DecorateScriptServiceV2With(b => b.BeforeCompleteScript(async (_, _, _) => +// { +// list.Add(1); +// await Task.CompletedTask; +// })) +// .DecorateScriptServiceV2With(b => b.BeforeCompleteScript(async (_, _, _) => +// { +// list.Add(2); +// await Task.CompletedTask; +// })) +// .DecorateScriptServiceV2With(b => b.DecorateCompleteScriptWith(async (inner, command, options) => +// { +// await Task.CompletedTask; +// await inner.CompleteScriptAsync(command, options); +// list.Add(3); +// })) +// .DecorateScriptServiceV2With(b => b.DecorateCompleteScriptWith(async (inner, command, options) => +// { +// await Task.CompletedTask; +// await inner.CompleteScriptAsync(command, options); +// list.Add(4); +// })) +// .Build(); +// +// await decorator.Decorate(new NoOPClientScriptService()).CompleteScriptAsync(SomeCompleteScriptCommandV2(), SomeHalibutProxyRequestOptions()); +// +// list.Should().BeEquivalentTo(new List {1, 2, 3, 4}); +// } +// +// [Test] +// public void ProxyGeneratedDecoratorsAreFirst() +// { +// var list = new List(); +// IRecordedMethodUsages recordedUsages = new MethodUsages(); +// long startedCountInCall = 0; +// var decorator = new TentacleServiceDecoratorBuilder() +// .DecorateScriptServiceV2With(b => b.BeforeCompleteScript(async (_, _, _) => +// { +// await Task.CompletedTask; +// // We should find that the proxy decorator has already counted this call. +// startedCountInCall = recordedUsages.ForAll().Started; +// // If the proxy decorator after this then throwing an exception here would result in calls not being counted. +// throw new Exception(); +// }) +// .Build()) +// .RecordMethodUsages(out recordedUsages) +// .Build(); +// +// Assert.ThrowsAsync(async () => await decorator.Decorate(new NoOPClientScriptService()).CompleteScriptAsync(SomeCompleteScriptCommandV2(), SomeHalibutProxyRequestOptions())); +// +// var usage = recordedUsages.ForAll(); +// +// usage.LastException.Should().NotBeNull(); +// usage.Started.Should().Be(1); +// startedCountInCall.Should().Be(1, "Because the proxy decorator which count calls should happen before any method specific decorators."); +// } +// +// static CompleteScriptCommandV2 SomeCompleteScriptCommandV2() +// { +// return new CompleteScriptCommandV2(new ScriptTicket("a")); +// } +// +// static HalibutProxyRequestOptions SomeHalibutProxyRequestOptions() +// { +// return new HalibutProxyRequestOptions(CancellationToken.None); +// } +// +// class NoOPClientScriptService : IAsyncClientScriptServiceV2 +// { +// public Task StartScriptAsync(StartScriptCommandV2 command, HalibutProxyRequestOptions proxyRequestOptions) +// { +// throw new NotImplementedException(); +// } +// +// public Task GetStatusAsync(ScriptStatusRequestV2 request, HalibutProxyRequestOptions proxyRequestOptions) +// { +// throw new NotImplementedException(); +// } +// +// public Task CancelScriptAsync(CancelScriptCommandV2 command, HalibutProxyRequestOptions proxyRequestOptions) +// { +// throw new NotImplementedException(); +// } +// +// public async Task CompleteScriptAsync(CompleteScriptCommandV2 command, HalibutProxyRequestOptions proxyRequestOptions) +// { +// await Task.CompletedTask; +// } +// } +// } +// } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/TentacleClientObserver.cs b/source/Octopus.Tentacle.Tests.Integration/TentacleClientObserver.cs index bbc2955a9..1bc2b8a81 100644 --- a/source/Octopus.Tentacle.Tests.Integration/TentacleClientObserver.cs +++ b/source/Octopus.Tentacle.Tests.Integration/TentacleClientObserver.cs @@ -1,160 +1,160 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils.Builders; -using Octopus.Tentacle.Contracts.ClientServices; -using Octopus.Tentacle.Contracts.Logging; -using Octopus.Tentacle.Contracts.Observability; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class TentacleClientObserver : IntegrationTest - { - [Test] - [TentacleConfigurations] - public async Task AnErrorDuringTheCallbackTo_ExecuteScriptCompleted_ShouldNotThrowOrStopScriptExecution(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - // Arrange - var tentacleClientObserver = new BrokenTentacleClientObserver(errorOnExecuteScriptCompleted: true); - - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleClientObserver(tentacleClientObserver) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b.Print("Hello")) - .Build(); - - // Act - await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - - // Assert - // We should have completed the script and not failed due to the error thrown by the TentacleClientObserver - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); - } - - [Test] - [TentacleConfigurations] - public async Task AnErrorDuringTheCallbackTo_RpcCallComplete_ShouldNotThrowOrStopScriptExecution(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - // Arrange - var tentacleClientObserver = new BrokenTentacleClientObserver(errorOnRpcCallCompleted: true); - - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleClientObserver(tentacleClientObserver) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) - .Build()) - .Build(CancellationToken); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(b => b.Print("Hello")) - .Build(); - - // Act - await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - - // Assert - // We should have completed the script and not failed due to the error thrown by the TentacleClientObserver - recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); - } - - [Test] - [TentacleConfigurations] - public async Task AnErrorDuringTheCallbackTo_UploadFileCompleted_ShouldNotThrowAnExecution(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - // Arrange - var tentacleClientObserver = new BrokenTentacleClientObserver(errorOnUploadFileCompleted: true); - - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleClientObserver(tentacleClientObserver) - .Build(CancellationToken); - - var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); - - // Act + Assert - await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); - } - - [Test] - [TentacleConfigurations] - public async Task AnErrorDuringTheCallbackTo_DownloadFileCompleted_ShouldNotThrowAnExecution(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - // Arrange - var tentacleClientObserver = new BrokenTentacleClientObserver(errorOnDownloadFileCompleted: true); - - await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacleClientObserver(tentacleClientObserver) - .Build(CancellationToken); - - var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "DownloadFile.txt"); - await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); - - // Act + Assert - await clientTentacle.TentacleClient.DownloadFile(remotePath, CancellationToken); - } - - class BrokenTentacleClientObserver : ITentacleClientObserver - { - private readonly bool errorOnRpcCallCompleted; - private readonly bool errorOnUploadFileCompleted; - private readonly bool errorOnDownloadFileCompleted; - private readonly bool errorOnExecuteScriptCompleted; - - public BrokenTentacleClientObserver( - bool errorOnRpcCallCompleted = false, - bool errorOnUploadFileCompleted = false, - bool errorOnDownloadFileCompleted = false, - bool errorOnExecuteScriptCompleted = false) - { - this.errorOnRpcCallCompleted = errorOnRpcCallCompleted; - this.errorOnUploadFileCompleted = errorOnUploadFileCompleted; - this.errorOnDownloadFileCompleted = errorOnDownloadFileCompleted; - this.errorOnExecuteScriptCompleted = errorOnExecuteScriptCompleted; - } - - public void RpcCallCompleted(RpcCallMetrics metrics, ITentacleClientTaskLog logger) - { - if (errorOnRpcCallCompleted) - { - throw new Exception($"RpcCallCompleted {Guid.NewGuid()}"); - } - } - - public void UploadFileCompleted(ClientOperationMetrics clientOperationMetrics, ITentacleClientTaskLog logger) - { - if (errorOnUploadFileCompleted) - { - throw new Exception($"UploadFileCompleted {Guid.NewGuid()}"); - } - } - - public void DownloadFileCompleted(ClientOperationMetrics clientOperationMetrics, ITentacleClientTaskLog logger) - { - if (errorOnDownloadFileCompleted) - { - throw new Exception($"DownloadFileCompleted {Guid.NewGuid()}"); - } - } - - public void ExecuteScriptCompleted(ClientOperationMetrics clientOperationMetrics, ITentacleClientTaskLog logger) - { - if (errorOnExecuteScriptCompleted) - { - throw new Exception($"ExecuteScriptCompleted {Guid.NewGuid()}"); - } - } - } - } -} +// using System; +// using System.IO; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils.Builders; +// using Octopus.Tentacle.Contracts.ClientServices; +// using Octopus.Tentacle.Contracts.Logging; +// using Octopus.Tentacle.Contracts.Observability; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class TentacleClientObserver : IntegrationTest +// { +// [Test] +// [TentacleConfigurations] +// public async Task AnErrorDuringTheCallbackTo_ExecuteScriptCompleted_ShouldNotThrowOrStopScriptExecution(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// // Arrange +// var tentacleClientObserver = new BrokenTentacleClientObserver(errorOnExecuteScriptCompleted: true); +// +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleClientObserver(tentacleClientObserver) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b.Print("Hello")) +// .Build(); +// +// // Act +// await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// +// // Assert +// // We should have completed the script and not failed due to the error thrown by the TentacleClientObserver +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task AnErrorDuringTheCallbackTo_RpcCallComplete_ShouldNotThrowOrStopScriptExecution(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// // Arrange +// var tentacleClientObserver = new BrokenTentacleClientObserver(errorOnRpcCallCompleted: true); +// +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleClientObserver(tentacleClientObserver) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) +// .Build()) +// .Build(CancellationToken); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(b => b.Print("Hello")) +// .Build(); +// +// // Act +// await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// +// // Assert +// // We should have completed the script and not failed due to the error thrown by the TentacleClientObserver +// recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task AnErrorDuringTheCallbackTo_UploadFileCompleted_ShouldNotThrowAnExecution(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// // Arrange +// var tentacleClientObserver = new BrokenTentacleClientObserver(errorOnUploadFileCompleted: true); +// +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleClientObserver(tentacleClientObserver) +// .Build(CancellationToken); +// +// var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "UploadFile.txt"); +// +// // Act + Assert +// await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); +// } +// +// [Test] +// [TentacleConfigurations] +// public async Task AnErrorDuringTheCallbackTo_DownloadFileCompleted_ShouldNotThrowAnExecution(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// // Arrange +// var tentacleClientObserver = new BrokenTentacleClientObserver(errorOnDownloadFileCompleted: true); +// +// await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacleClientObserver(tentacleClientObserver) +// .Build(CancellationToken); +// +// var remotePath = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "DownloadFile.txt"); +// await clientTentacle.TentacleClient.UploadFile(remotePath, DataStream.FromString("Hello"), CancellationToken); +// +// // Act + Assert +// await clientTentacle.TentacleClient.DownloadFile(remotePath, CancellationToken); +// } +// +// class BrokenTentacleClientObserver : ITentacleClientObserver +// { +// private readonly bool errorOnRpcCallCompleted; +// private readonly bool errorOnUploadFileCompleted; +// private readonly bool errorOnDownloadFileCompleted; +// private readonly bool errorOnExecuteScriptCompleted; +// +// public BrokenTentacleClientObserver( +// bool errorOnRpcCallCompleted = false, +// bool errorOnUploadFileCompleted = false, +// bool errorOnDownloadFileCompleted = false, +// bool errorOnExecuteScriptCompleted = false) +// { +// this.errorOnRpcCallCompleted = errorOnRpcCallCompleted; +// this.errorOnUploadFileCompleted = errorOnUploadFileCompleted; +// this.errorOnDownloadFileCompleted = errorOnDownloadFileCompleted; +// this.errorOnExecuteScriptCompleted = errorOnExecuteScriptCompleted; +// } +// +// public void RpcCallCompleted(RpcCallMetrics metrics, ITentacleClientTaskLog logger) +// { +// if (errorOnRpcCallCompleted) +// { +// throw new Exception($"RpcCallCompleted {Guid.NewGuid()}"); +// } +// } +// +// public void UploadFileCompleted(ClientOperationMetrics clientOperationMetrics, ITentacleClientTaskLog logger) +// { +// if (errorOnUploadFileCompleted) +// { +// throw new Exception($"UploadFileCompleted {Guid.NewGuid()}"); +// } +// } +// +// public void DownloadFileCompleted(ClientOperationMetrics clientOperationMetrics, ITentacleClientTaskLog logger) +// { +// if (errorOnDownloadFileCompleted) +// { +// throw new Exception($"DownloadFileCompleted {Guid.NewGuid()}"); +// } +// } +// +// public void ExecuteScriptCompleted(ClientOperationMetrics clientOperationMetrics, ITentacleClientTaskLog logger) +// { +// if (errorOnExecuteScriptCompleted) +// { +// throw new Exception($"ExecuteScriptCompleted {Guid.NewGuid()}"); +// } +// } +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/TentacleCommandLineTests.cs b/source/Octopus.Tentacle.Tests.Integration/TentacleCommandLineTests.cs index fba7ceedb..8b5bc800a 100644 --- a/source/Octopus.Tentacle.Tests.Integration/TentacleCommandLineTests.cs +++ b/source/Octopus.Tentacle.Tests.Integration/TentacleCommandLineTests.cs @@ -30,245 +30,245 @@ namespace Octopus.Tentacle.Tests.Integration [IntegrationTestTimeout] public class TentacleCommandLineTests : IntegrationTest { - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task TentacleExeNoArguments(TentacleConfigurationTestCase tc) - { - var (exitCode, stdout, stderr) = await RunCommand(tc, null); - - exitCode.Should().Be(2, "the exit code should be 2 if the command wasn't understood"); - stdout.Should().StartWithEquivalentOf("Usage: Tentacle []", "should show help by default if no other commands are specified"); - stdout.Should().ContainEquivalentOf("Or use --help for more details.", "should provide the hint for command-specific help"); - stderr.Should().BeNullOrEmpty(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task UnknownCommand(TentacleConfigurationTestCase tc) - { - var (exitCode, stdout, stderr) = await RunCommand(tc, null, "unknown-command"); - - exitCode.Should().Be(2, "the exit code should be 2 if the command wasn't understood"); - stderr.Should().StartWithEquivalentOf("Command 'unknown-command' is not supported", "the error should clearly indicate the command which is not understood"); - stdout.Should().StartWithEquivalentOf("See 'Tentacle help'", "should provide the hint to use help"); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task UnknownArgument(TentacleConfigurationTestCase tc) - { - var (exitCode, stdout, stderr) = await RunCommand(tc, null, "version", "--unknown=argument"); - - exitCode.Should().Be(1, "the exit code should be 1 if the command has unknown arguments"); - stdout.Should().BeNullOrEmpty("the error message should be written to stderr, not stdout"); - stderr.Should().ContainEquivalentOf("Unrecognized command line arguments: --unknown=argument", "the error message (written to stderr) should clearly indicate which arguments couldn't be parsed."); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task InvalidArgument(TentacleConfigurationTestCase tc) - { - var (exitCode, stdout, stderr) = await RunCommand(tc, null, "version", "--format=unsupported"); - - exitCode.Should().Be(1, "the exit code should be 1 if the command has unknown arguments"); - stdout.Should().BeNullOrEmpty("the error message should be written to stderr, not stdout"); - stderr.Should().ContainEquivalentOf("The format 'unsupported' is not supported. Try text or json.", "the error message (written to stderr) should clearly indicate which argument was invalid."); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task NoConsoleLoggingSwitchStillSilentlySupportedForBackwardsCompat(TentacleConfigurationTestCase tc) - { - var (_, _, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--noconsolelogging"); - - stderr.Should().BeNullOrEmpty(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task NoLogoSwitchStillSilentlySupportedForBackwardsCompat(TentacleConfigurationTestCase tc) - { - var (_, _, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--nologo"); - - stderr.Should().BeNullOrEmpty(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task ConsoleSwitchStillSilentlySupportedForBackwardsCompat(TentacleConfigurationTestCase tc) - { - var (_, _, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--console"); - - stderr.Should().BeNullOrEmpty(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task ShouldSupportFuzzyCommandParsing(TentacleConfigurationTestCase tc) - { - await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version"); - await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "--version"); - await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "/version"); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task VersionCommandTextFormat(TentacleConfigurationTestCase tc) - { - var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version"); - - var expectedVersion = GetVersionInfo(tc); - - stdout.Should().Be(expectedVersion.ProductVersion, "The version command should print the informational version as text"); - stderr.Should().BeNullOrEmpty(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task VersionCommandJsonFormat(TentacleConfigurationTestCase tc) - { - var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--format=json"); - - var expectedVersion = GetVersionInfo(tc); - var output = JObject.Parse(stdout); - - output["InformationalVersion"].Value().Should().Be(expectedVersion.ProductVersion, "The version command should print the informational version in the JSON output"); - output["MajorMinorPatch"].Value().Should().Be($"{expectedVersion.FileMajorPart}.{expectedVersion.FileMinorPart}.{expectedVersion.FileBuildPart}", "The version command should print the version in the json output"); - output["NuGetVersion"].Value().Should().NotBeNull("The version command should print the NuGet version in the JSON output"); - output["SourceBranchName"].Value().Should().NotBeNull("The version command should print the source branch in the JSON output"); - - stderr.Should().BeNullOrEmpty(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task CanGetHelpForHelp(TentacleConfigurationTestCase tc) - { - var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "help", "--help"); - stderr.Should().BeNullOrEmpty(); - - stdout.Should().Be( -@"Usage: Tentacle help [] - -Where [] is any of: - - --format=VALUE The format of the output (text,json). Defaults - to text. - -Or one of the common options: - - --help Show detailed help for this command -"); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task HelpAsSwitchShouldShowCommandSpecificHelp(TentacleConfigurationTestCase tc) - { - var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--help"); - stderr.Should().BeNullOrEmpty(); - - stdout.Should().Be( - @"Usage: Tentacle version [] - -Where [] is any of: - - --format=VALUE The format of the output (text,json). Defaults - to text. - -Or one of the common options: - - --help Show detailed help for this command -"); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task GeneralHelpAsJsonCanBeParsedByAutomationScripts(TentacleConfigurationTestCase tc) - { - var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "help", "--format=json"); - - stderr.Should().BeNullOrEmpty(); - var help = JsonConvert.DeserializeAnonymousType( - stdout, - new - { - Commands = new[] - { - new - { - Name = "", - Description = "", - Aliases = Array.Empty() - } - } - }); - - help.Commands.Select(c => c.Name) - .Should() - .Contain( - "configure", - "help", - "run", - "version", - "show-master-key", - "show-thumbprint"); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task CommandSpecificHelpAsJsonCanBeParsedByAutomationScripts(TentacleConfigurationTestCase tc) - { - var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--help", "--format=json"); - - stderr.Should().BeNullOrEmpty(); - var help = JsonConvert.DeserializeAnonymousType( - stdout, - new - { - Name = "", - Description = "", - Aliases = Array.Empty(), - Options = new[] - { - new - { - Name = "", - Description = "" - } - } - }); - - help.Name.Should().Be("version"); - help.Options.Select(o => o.Name).Should().Contain("format"); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task CommandSpecificHelpAsJsonLooksSensibleToHumans(TentacleConfigurationTestCase tc) - { - var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--help", "--format=json"); - stderr.Should().BeNullOrEmpty(); - - stdout.Should().Be( -@"{ - ""Name"": ""version"", - ""Description"": ""Show the Tentacle version information"", - ""Aliases"": [], - ""Options"": [ - { - ""Name"": ""format"", - ""Description"": ""The format of the output (text,json). Defaults to text."" - } - ], - ""CommonOptions"": [ - { - ""Name"": ""help"", - ""Description"": ""Show detailed help for this command"" - } - ] -}"); - } +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task TentacleExeNoArguments(TentacleConfigurationTestCase tc) +// { +// var (exitCode, stdout, stderr) = await RunCommand(tc, null); +// +// exitCode.Should().Be(2, "the exit code should be 2 if the command wasn't understood"); +// stdout.Should().StartWithEquivalentOf("Usage: Tentacle []", "should show help by default if no other commands are specified"); +// stdout.Should().ContainEquivalentOf("Or use --help for more details.", "should provide the hint for command-specific help"); +// stderr.Should().BeNullOrEmpty(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task UnknownCommand(TentacleConfigurationTestCase tc) +// { +// var (exitCode, stdout, stderr) = await RunCommand(tc, null, "unknown-command"); +// +// exitCode.Should().Be(2, "the exit code should be 2 if the command wasn't understood"); +// stderr.Should().StartWithEquivalentOf("Command 'unknown-command' is not supported", "the error should clearly indicate the command which is not understood"); +// stdout.Should().StartWithEquivalentOf("See 'Tentacle help'", "should provide the hint to use help"); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task UnknownArgument(TentacleConfigurationTestCase tc) +// { +// var (exitCode, stdout, stderr) = await RunCommand(tc, null, "version", "--unknown=argument"); +// +// exitCode.Should().Be(1, "the exit code should be 1 if the command has unknown arguments"); +// stdout.Should().BeNullOrEmpty("the error message should be written to stderr, not stdout"); +// stderr.Should().ContainEquivalentOf("Unrecognized command line arguments: --unknown=argument", "the error message (written to stderr) should clearly indicate which arguments couldn't be parsed."); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task InvalidArgument(TentacleConfigurationTestCase tc) +// { +// var (exitCode, stdout, stderr) = await RunCommand(tc, null, "version", "--format=unsupported"); +// +// exitCode.Should().Be(1, "the exit code should be 1 if the command has unknown arguments"); +// stdout.Should().BeNullOrEmpty("the error message should be written to stderr, not stdout"); +// stderr.Should().ContainEquivalentOf("The format 'unsupported' is not supported. Try text or json.", "the error message (written to stderr) should clearly indicate which argument was invalid."); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task NoConsoleLoggingSwitchStillSilentlySupportedForBackwardsCompat(TentacleConfigurationTestCase tc) +// { +// var (_, _, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--noconsolelogging"); +// +// stderr.Should().BeNullOrEmpty(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task NoLogoSwitchStillSilentlySupportedForBackwardsCompat(TentacleConfigurationTestCase tc) +// { +// var (_, _, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--nologo"); +// +// stderr.Should().BeNullOrEmpty(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task ConsoleSwitchStillSilentlySupportedForBackwardsCompat(TentacleConfigurationTestCase tc) +// { +// var (_, _, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--console"); +// +// stderr.Should().BeNullOrEmpty(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task ShouldSupportFuzzyCommandParsing(TentacleConfigurationTestCase tc) +// { +// await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version"); +// await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "--version"); +// await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "/version"); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task VersionCommandTextFormat(TentacleConfigurationTestCase tc) +// { +// var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version"); +// +// var expectedVersion = GetVersionInfo(tc); +// +// stdout.Should().Be(expectedVersion.ProductVersion, "The version command should print the informational version as text"); +// stderr.Should().BeNullOrEmpty(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task VersionCommandJsonFormat(TentacleConfigurationTestCase tc) +// { +// var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--format=json"); +// +// var expectedVersion = GetVersionInfo(tc); +// var output = JObject.Parse(stdout); +// +// output["InformationalVersion"].Value().Should().Be(expectedVersion.ProductVersion, "The version command should print the informational version in the JSON output"); +// output["MajorMinorPatch"].Value().Should().Be($"{expectedVersion.FileMajorPart}.{expectedVersion.FileMinorPart}.{expectedVersion.FileBuildPart}", "The version command should print the version in the json output"); +// output["NuGetVersion"].Value().Should().NotBeNull("The version command should print the NuGet version in the JSON output"); +// output["SourceBranchName"].Value().Should().NotBeNull("The version command should print the source branch in the JSON output"); +// +// stderr.Should().BeNullOrEmpty(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task CanGetHelpForHelp(TentacleConfigurationTestCase tc) +// { +// var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "help", "--help"); +// stderr.Should().BeNullOrEmpty(); +// +// stdout.Should().Be( +// @"Usage: Tentacle help [] +// +// Where [] is any of: +// +// --format=VALUE The format of the output (text,json). Defaults +// to text. +// +// Or one of the common options: +// +// --help Show detailed help for this command +// "); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task HelpAsSwitchShouldShowCommandSpecificHelp(TentacleConfigurationTestCase tc) +// { +// var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--help"); +// stderr.Should().BeNullOrEmpty(); +// +// stdout.Should().Be( +// @"Usage: Tentacle version [] +// +// Where [] is any of: +// +// --format=VALUE The format of the output (text,json). Defaults +// to text. +// +// Or one of the common options: +// +// --help Show detailed help for this command +// "); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task GeneralHelpAsJsonCanBeParsedByAutomationScripts(TentacleConfigurationTestCase tc) +// { +// var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "help", "--format=json"); +// +// stderr.Should().BeNullOrEmpty(); +// var help = JsonConvert.DeserializeAnonymousType( +// stdout, +// new +// { +// Commands = new[] +// { +// new +// { +// Name = "", +// Description = "", +// Aliases = Array.Empty() +// } +// } +// }); +// +// help.Commands.Select(c => c.Name) +// .Should() +// .Contain( +// "configure", +// "help", +// "run", +// "version", +// "show-master-key", +// "show-thumbprint"); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task CommandSpecificHelpAsJsonCanBeParsedByAutomationScripts(TentacleConfigurationTestCase tc) +// { +// var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--help", "--format=json"); +// +// stderr.Should().BeNullOrEmpty(); +// var help = JsonConvert.DeserializeAnonymousType( +// stdout, +// new +// { +// Name = "", +// Description = "", +// Aliases = Array.Empty(), +// Options = new[] +// { +// new +// { +// Name = "", +// Description = "" +// } +// } +// }); +// +// help.Name.Should().Be("version"); +// help.Options.Select(o => o.Name).Should().Contain("format"); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task CommandSpecificHelpAsJsonLooksSensibleToHumans(TentacleConfigurationTestCase tc) +// { +// var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "version", "--help", "--format=json"); +// stderr.Should().BeNullOrEmpty(); +// +// stdout.Should().Be( +// @"{ +// ""Name"": ""version"", +// ""Description"": ""Show the Tentacle version information"", +// ""Aliases"": [], +// ""Options"": [ +// { +// ""Name"": ""format"", +// ""Description"": ""The format of the output (text,json). Defaults to text."" +// } +// ], +// ""CommonOptions"": [ +// { +// ""Name"": ""help"", +// ""Description"": ""Show detailed help for this command"" +// } +// ] +// }"); +// } [Test] [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] @@ -292,6 +292,7 @@ public async Task HelpForInstanceSpecificCommandsAlwaysWorks(TentacleConfigurati } }); + help.Commands.Should().HaveCountGreaterThan(0); var failed = help.Commands.Select(async c => @@ -327,417 +328,440 @@ public async Task HelpForInstanceSpecificCommandsAlwaysWorks(TentacleConfigurati The details are logged above. These commands probably need to take Lazy dependencies so they can be instantiated for showing help without requiring every dependency to be resolvable."); } } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - // Run these tests in serial to avoid conflicts - [NonParallelizable] - public async Task InvalidInstance(TentacleConfigurationTestCase tc) - { - var (exitCode, stdout, stderr) = await RunCommand( - tc, - null, - "show-thumbprint", "--instance=invalidinstance"); - - exitCode.Should().Be(1, $"the exit code should be 1 if the instance is not able to be resolved"); - stderr.Should().ContainEquivalentOf("Instance invalidinstance of tentacle has not been configured", "the error message should make it clear the instance has not been configured"); - stderr.Should().ContainEquivalentOf("Available instances:", "should provide a hint as to which instances are available on the machine"); - stdout.Should().BeNullOrEmpty(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - // Run these tests in serial to avoid conflicts - [NonParallelizable] - public async Task ShowThumbprintCommandText(TentacleConfigurationTestCase tc) - { - await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); - await clientAndTentacle.RunningTentacle.Stop(CancellationToken); - var (exitCode, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( - tc, - clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, - "show-thumbprint", $"--instance={clientAndTentacle.RunningTentacle.InstanceName}"); - - exitCode.Should().Be(0, $"we expected the command to succeed.\r\nStdErr: '{stderr}'\r\nStdOut: '{stdout}'"); - stdout.Should().Be(TestCertificates.TentaclePublicThumbprint, "the thumbprint should be written directly to stdout"); - stderr.Should().BeNullOrEmpty(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - // Run these tests in serial to avoid conflicts - [NonParallelizable] - public async Task ShowThumbprintCommandJson(TentacleConfigurationTestCase tc) - { - await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); - await clientAndTentacle.RunningTentacle.Stop(CancellationToken); - var (exitCode, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( - tc, - clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, - "show-thumbprint", $"--instance={clientAndTentacle.RunningTentacle.InstanceName}", "--format=json"); - - exitCode.Should().Be(0, $"we expected the command to succeed.\r\nStdErr: '{stderr}'\r\nStdOut: '{stdout}'"); - stdout.Should().Be(JsonConvert.SerializeObject(new { Thumbprint = TestCertificates.TentaclePublicThumbprint }), "the thumbprint should be written directly to stdout as JSON"); - stderr.Should().BeNullOrEmpty(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - // Run these tests in serial to avoid conflicts - [NonParallelizable] - public async Task ListInstancesCommandText(TentacleConfigurationTestCase tc) - { - await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); - await clientAndTentacle.RunningTentacle.Stop(CancellationToken); - var (exitCode, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( - tc, - clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, - "list-instances", "--format=text"); - - exitCode.Should().Be(0, $"we expected the command to succeed.\r\nStdErr: '{stderr}'\r\nStdOut: '{stdout}'"); - var configPath = Path.Combine(clientAndTentacle.RunningTentacle.HomeDirectory, clientAndTentacle.RunningTentacle.InstanceName + ".cfg"); - stdout.Should().Contain($"Instance '{clientAndTentacle.RunningTentacle.InstanceName}' uses configuration '{configPath}'.", "the current instance should be listed"); - stderr.Should().BeNullOrEmpty(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - // Run these tests in serial to avoid conflicts - [NonParallelizable] - public async Task ListInstancesCommandJson(TentacleConfigurationTestCase tc) - { - await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); - await clientAndTentacle.RunningTentacle.Stop(CancellationToken); - var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( - tc, - clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, - "list-instances", "--format=json"); - - stdout.Should().Contain($"\"InstanceName\": \"{clientAndTentacle.RunningTentacle.InstanceName}\"", "the current instance should be listed"); - var configPath = Path.Combine(clientAndTentacle.RunningTentacle.HomeDirectory, clientAndTentacle.RunningTentacle.InstanceName + ".cfg"); - var jsonFormattedPath = JsonFormattedPath(configPath); - stdout.Should().Contain($"\"ConfigurationFilePath\": \"{jsonFormattedPath}\"", "the path to the config file for the current instance should be listed"); - stderr.Should().BeNullOrEmpty(); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - // Run these tests in serial to avoid conflicts - [NonParallelizable] - public async Task ShouldLogStartupDiagnosticsToInstanceLogFileOnly(TentacleConfigurationTestCase tc) - { - await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); - await clientAndTentacle.RunningTentacle.Stop(CancellationToken); - - var startingLogText = clientAndTentacle.RunningTentacle.ReadAllLogFileText(); - - var (exitCode, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( - tc, - clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, - "show-thumbprint", $"--instance={clientAndTentacle.RunningTentacle.InstanceName}"); - - try - { - var logFileText = Policy - .Handle() - .WaitAndRetry( - 20, - i => TimeSpan.FromMilliseconds(100 * i), - (exception, _) => { Logger.Information($"Failed to get new log content: {exception.Message}. Retrying!"); }) - .Execute( - () => - { - var wholeLog = clientAndTentacle.RunningTentacle.ReadAllLogFileText(); - var newLog = wholeLog.Replace(startingLogText, string.Empty); - if (string.IsNullOrWhiteSpace(newLog) || !newLog.Contains("CommandLine:")) - { - throw new NotLoggedYetException(); - } - return newLog; - }); - - logFileText.Should().ContainEquivalentOf($"OperatingSystem: {RuntimeInformation.OSDescription}", "the OSVersion should be in our diagnostics"); - logFileText.Should().ContainEquivalentOf("OperatingSystem:", "the OSVersion should be in our diagnostics"); - logFileText.Should().ContainEquivalentOf($"OsBitVersion: {(Environment.Is64BitOperatingSystem ? "x64" : "x86")}", "the OsBitVersion should be in our diagnostics"); - logFileText.Should().ContainEquivalentOf($"Is64BitProcess: {Environment.Is64BitProcess}", "the Is64BitProcess should be in our diagnostics"); - - if (PlatformDetection.IsRunningOnWindows) - { -#pragma warning disable CA1416 - logFileText.Should().ContainEquivalentOf($"CurrentUser: {WindowsIdentity.GetCurrent().Name}", "the CurrentUser should be in our diagnostics"); -#pragma warning disable CA1416 - } - else - { - logFileText.Should().ContainEquivalentOf($"CurrentUser: {Environment.UserName}", "the CurrentUser should be in our diagnostics"); - } - - logFileText.Should().ContainEquivalentOf($"MachineName: {Environment.MachineName}", "the MachineName should be in our diagnostics"); - logFileText.Should().ContainEquivalentOf($"ProcessorCount: {Environment.ProcessorCount}", "the ProcessorCount should be in our diagnostics"); - logFileText.Should().ContainEquivalentOf($"CurrentDirectory: {Directory.GetCurrentDirectory()}", "the CurrentDirectory should be in our diagnostics"); - logFileText.Should().ContainEquivalentOf($"TempDirectory: {Path.GetTempPath()}", "the TempDirectory should be in our diagnostics"); - logFileText.Should().ContainEquivalentOf("HostProcessName: ", "the HostProcessName should be in our diagnostics"); - stdout.Should().NotContainEquivalentOf($"{RuntimeInformation.OSDescription}", "the OSVersion should not be written to stdout"); - await Task.CompletedTask; - } - catch (NotLoggedYetException) - { - Logger.Error("Failed to get new log content"); - Logger.Error($"Process exit code {exitCode}"); - Logger.Error($"Starting log text: {Environment.NewLine}{startingLogText}"); - Logger.Error($"Current log text: {Environment.NewLine}{clientAndTentacle.RunningTentacle.ReadAllLogFileText()}"); - Logger.Error($"Command StdOut: {Environment.NewLine}{stdout}"); - Logger.Error($"Command StdErr: {Environment.NewLine}{stderr}"); - - throw; - } - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - public async Task HelpAsFirstArgumentShouldShowCommandSpecificHelp(TentacleConfigurationTestCase tc) - { - var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "help", "version"); - stderr.Should().BeNullOrEmpty(); - - stdout.Should().Be( -@"Usage: Tentacle version [] - -Where [] is any of: - - --format=VALUE The format of the output (text,json). Defaults - to text. - -Or one of the common options: - - --help Show detailed help for this command -"); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - // Run these tests in serial to avoid conflicts - [NonParallelizable] - public async Task ShowConfigurationCommand(TentacleConfigurationTestCase tc) - { - await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); - await clientAndTentacle.RunningTentacle.Stop(CancellationToken); - var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( - tc, - clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, - "show-configuration", $"--instance={clientAndTentacle.RunningTentacle.InstanceName}"); - - stderr.Should().BeNullOrEmpty(); - - // Actually parse and query the document just like our consumer will - dynamic? settings = JsonConvert.DeserializeObject(stdout); - - ((string)settings.Octopus.Home).Should().Be(clientAndTentacle.RunningTentacle.HomeDirectory, "the home directory should match"); - ((string)settings.Tentacle.Deployment.ApplicationDirectory).Should().Be(clientAndTentacle.RunningTentacle.ApplicationDirectory, "the application directory should match"); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - // Run these tests in serial to avoid conflicts - [NonParallelizable] - public async Task ShowConfigurationCommandOnPartiallyConfiguredTentacle(TentacleConfigurationTestCase tc) - { - using var homeDirectory = new TemporaryDirectory(); - var environmentVariables = new Dictionary { { EnvironmentVariables.TentacleMachineConfigurationHomeDirectory, homeDirectory.DirectoryPath } }; - - var instanceId = Guid.NewGuid().ToString(); - using var temporaryDirectory = new TemporaryDirectory(); - await RunCommandAndAssertExitsWithSuccessExitCode( - tc, - environmentVariables, - "create-instance", $"--instance={instanceId}", "--config", Path.Combine(temporaryDirectory.DirectoryPath, instanceId + ".cfg")); - - var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( - tc, - environmentVariables, - "show-configuration", - $"--instance={instanceId}"); - - stderr.Should().BeNullOrEmpty(); - - // Actually parse and query the document just like our consumer will - dynamic? settings = JsonConvert.DeserializeObject(stdout); - ((string)settings.Octopus.Home).Should().Be(temporaryDirectory.DirectoryPath, "the home directory should match"); - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - // Run these tests in serial to avoid conflicts - [NonParallelizable] - public async Task ShowConfigurationCommandLooksSensibleToHumans(TentacleConfigurationTestCase tc) - { - await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); - await clientAndTentacle.RunningTentacle.Stop(CancellationToken); - var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( - tc, - clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, - "show-configuration", $"--instance={clientAndTentacle.RunningTentacle.InstanceName}"); - - stderr.Should().BeNullOrEmpty(); - - if (tc.TentacleType == TentacleType.Polling) - { - stdout.Should().Be($@"{{ - ""Octopus"": {{ - ""Home"": ""{JsonFormattedPath(clientAndTentacle.RunningTentacle.HomeDirectory)}"", - ""Watchdog"": {{ - ""Enabled"": false, - ""Instances"": ""*"", - ""Interval"": 0 - }} - }}, - ""Tentacle"": {{ - ""CertificateThumbprint"": ""{clientAndTentacle.RunningTentacle.Thumbprint}"", - ""Communication"": {{ - ""TrustedOctopusServers"": [ - {{ - ""Thumbprint"": ""{clientAndTentacle.Server.Thumbprint}"", - ""CommunicationStyle"": 2, - ""Address"": ""https://localhost:{clientAndTentacle.Server.ServerListeningPort}"", - ""Squid"": null, - ""SubscriptionId"": ""{clientAndTentacle.RunningTentacle.ServiceUri}"" - }} - ] - }}, - ""Deployment"": {{ - ""ApplicationDirectory"": ""{JsonFormattedPath(clientAndTentacle.RunningTentacle.ApplicationDirectory)}"" - }}, - ""Services"": {{ - ""ListenIP"": null, - ""NoListen"": true, - ""PortNumber"": 10933 - }} - }} -}} -"); - } - else - { - stdout.Should().Be($@"{{ - ""Octopus"": {{ - ""Home"": ""{JsonFormattedPath(clientAndTentacle.RunningTentacle.HomeDirectory)}"", - ""Watchdog"": {{ - ""Enabled"": false, - ""Instances"": ""*"", - ""Interval"": 0 - }} - }}, - ""Tentacle"": {{ - ""CertificateThumbprint"": ""{clientAndTentacle.RunningTentacle.Thumbprint}"", - ""Communication"": {{ - ""TrustedOctopusServers"": [ - {{ - ""Thumbprint"": ""{clientAndTentacle.Server.Thumbprint}"", - ""CommunicationStyle"": 1, - ""Address"": null, - ""Squid"": null, - ""SubscriptionId"": null - }} - ] - }}, - ""Deployment"": {{ - ""ApplicationDirectory"": ""{JsonFormattedPath(clientAndTentacle.RunningTentacle.ApplicationDirectory)}"" - }}, - ""Services"": {{ - ""ListenIP"": null, - ""NoListen"": false, - ""PortNumber"": {clientAndTentacle.RunningTentacle.ServiceUri.Port} - }} - }} -}} -"); - } - - await Task.CompletedTask; - } - - [Test] - [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] - [WindowsTest] - // Run these tests in serial to avoid conflicts - [NonParallelizable] - public async Task WatchdogCreateAndDeleteCommand(TentacleConfigurationTestCase tc) - { - await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); - await clientAndTentacle.RunningTentacle.Stop(CancellationToken); - var create = await RunCommandAndAssertExitsWithSuccessExitCode( - tc, - clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, - "watchdog", "--create", $"--instances={clientAndTentacle.RunningTentacle.InstanceName}"); - - create.StdError.Should().BeNullOrEmpty(); - create.StdOut.Should().ContainEquivalentOf("Creating watchdog task"); - var delete = await RunCommandAndAssertExitsWithSuccessExitCode( - tc, - clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, - "watchdog", "--delete", $"--instances={clientAndTentacle.RunningTentacle.InstanceName}"); - - delete.StdError.Should().BeNullOrEmpty(); - delete.StdOut.Should().ContainEquivalentOf("Removing watchdog task"); - } - - FileVersionInfo GetVersionInfo(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var tentacleExe = TentacleExeFinder.FindTentacleExe(tentacleConfigurationTestCase.TentacleRuntime); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return FileVersionInfo.GetVersionInfo(tentacleExe); - } - - //todo: change this to trust the value set in the context.TentacleExePath (will need a renovation of ExePathResolver to be non windows specific) - return FileVersionInfo.GetVersionInfo($"{tentacleExe}.dll"); - } - - async Task<(int ExitCode, string StdOut, string StdError)> RunCommandAndAssertExitsWithSuccessExitCode( - TentacleConfigurationTestCase tentacleConfigurationTestCase, - IReadOnlyDictionary? environmentVariables, - params string[] arguments) - { - var (exitCode, stdout, stderr) = await RunCommand(tentacleConfigurationTestCase, environmentVariables, arguments); - exitCode.Should().Be(0, $"we expected the command to succeed.\r\nStdErr: '{stderr}'\r\nStdOut: '{stdout}'"); - return (exitCode, stdout, stderr); - } - - async Task<(int ExitCode, string StdOut, string StdError)> RunCommand( - TentacleConfigurationTestCase tentacleConfigurationTestCase, - IReadOnlyDictionary? environmentVariables, - params string[] arguments) - { - using var tempDirectory = new TemporaryDirectory(); - - var environmentVariablesToRunTentacleWith = new Dictionary(); - - if (environmentVariables?.Any() == true) - { - environmentVariablesToRunTentacleWith.AddRange(environmentVariables); - } - - if (!environmentVariablesToRunTentacleWith.ContainsKey(EnvironmentVariables.TentacleMachineConfigurationHomeDirectory)) - { - environmentVariablesToRunTentacleWith.Add(EnvironmentVariables.TentacleMachineConfigurationHomeDirectory, tempDirectory.DirectoryPath); - } - - var tentacleExe = TentacleExeFinder.FindTentacleExe(tentacleConfigurationTestCase.TentacleRuntime); - var output = new StringBuilder(); - var errorOut = new StringBuilder(); - - var result = await RetryHelper.RetryAsync( - () => Cli.Wrap(tentacleExe) - .WithArguments(arguments) - .WithValidation(CommandResultValidation.None) - .WithStandardOutputPipe(PipeTarget.ToStringBuilder(output)) - .WithStandardErrorPipe(PipeTarget.ToStringBuilder(errorOut)) - .WithEnvironmentVariables(environmentVariablesToRunTentacleWith) - .ExecuteAsync(CancellationToken)); - - return (result.ExitCode, output.ToString(), errorOut.ToString()); - } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// // Run these tests in serial to avoid conflicts +// [NonParallelizable] +// public async Task InvalidInstance(TentacleConfigurationTestCase tc) +// { +// var (exitCode, stdout, stderr) = await RunCommand( +// tc, +// null, +// "show-thumbprint", "--instance=invalidinstance"); +// +// exitCode.Should().Be(1, $"the exit code should be 1 if the instance is not able to be resolved"); +// stderr.Should().ContainEquivalentOf("Instance invalidinstance of tentacle has not been configured", "the error message should make it clear the instance has not been configured"); +// stderr.Should().ContainEquivalentOf("Available instances:", "should provide a hint as to which instances are available on the machine"); +// stdout.Should().BeNullOrEmpty(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// // Run these tests in serial to avoid conflicts +// [NonParallelizable] +// public async Task ShowThumbprintCommandText(TentacleConfigurationTestCase tc) +// { +// await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); +// await clientAndTentacle.RunningTentacle.Stop(CancellationToken); +// var (exitCode, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( +// tc, +// clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, +// "show-thumbprint", $"--instance={clientAndTentacle.RunningTentacle.InstanceName}"); +// +// exitCode.Should().Be(0, $"we expected the command to succeed.\r\nStdErr: '{stderr}'\r\nStdOut: '{stdout}'"); +// stdout.Should().Be(TestCertificates.TentaclePublicThumbprint, "the thumbprint should be written directly to stdout"); +// stderr.Should().BeNullOrEmpty(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// // Run these tests in serial to avoid conflicts +// [NonParallelizable] +// public async Task ShowThumbprintCommandJson(TentacleConfigurationTestCase tc) +// { +// await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); +// await clientAndTentacle.RunningTentacle.Stop(CancellationToken); +// var (exitCode, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( +// tc, +// clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, +// "show-thumbprint", $"--instance={clientAndTentacle.RunningTentacle.InstanceName}", "--format=json"); +// +// exitCode.Should().Be(0, $"we expected the command to succeed.\r\nStdErr: '{stderr}'\r\nStdOut: '{stdout}'"); +// stdout.Should().Be(JsonConvert.SerializeObject(new { Thumbprint = TestCertificates.TentaclePublicThumbprint }), "the thumbprint should be written directly to stdout as JSON"); +// stderr.Should().BeNullOrEmpty(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// // Run these tests in serial to avoid conflicts +// [NonParallelizable] +// public async Task ListInstancesCommandText(TentacleConfigurationTestCase tc) +// { +// await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); +// await clientAndTentacle.RunningTentacle.Stop(CancellationToken); +// var (exitCode, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( +// tc, +// clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, +// "list-instances", "--format=text"); +// +// exitCode.Should().Be(0, $"we expected the command to succeed.\r\nStdErr: '{stderr}'\r\nStdOut: '{stdout}'"); +// var configPath = Path.Combine(clientAndTentacle.RunningTentacle.HomeDirectory, clientAndTentacle.RunningTentacle.InstanceName + ".cfg"); +// stdout.Should().Contain($"Instance '{clientAndTentacle.RunningTentacle.InstanceName}' uses configuration '{configPath}'.", "the current instance should be listed"); +// stderr.Should().BeNullOrEmpty(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// // Run these tests in serial to avoid conflicts +// [NonParallelizable] +// public async Task ListInstancesCommandJson(TentacleConfigurationTestCase tc) +// { +// await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); +// await clientAndTentacle.RunningTentacle.Stop(CancellationToken); +// var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( +// tc, +// clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, +// "list-instances", "--format=json"); +// +// stdout.Should().Contain($"\"InstanceName\": \"{clientAndTentacle.RunningTentacle.InstanceName}\"", "the current instance should be listed"); +// var configPath = Path.Combine(clientAndTentacle.RunningTentacle.HomeDirectory, clientAndTentacle.RunningTentacle.InstanceName + ".cfg"); +// var jsonFormattedPath = JsonFormattedPath(configPath); +// stdout.Should().Contain($"\"ConfigurationFilePath\": \"{jsonFormattedPath}\"", "the path to the config file for the current instance should be listed"); +// stderr.Should().BeNullOrEmpty(); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// // Run these tests in serial to avoid conflicts +// [NonParallelizable] +// public async Task ShouldLogStartupDiagnosticsToInstanceLogFileOnly(TentacleConfigurationTestCase tc) +// { +// await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); +// await clientAndTentacle.RunningTentacle.Stop(CancellationToken); +// +// var startingLogText = clientAndTentacle.RunningTentacle.ReadAllLogFileText(); +// +// var (exitCode, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( +// tc, +// clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, +// "show-thumbprint", $"--instance={clientAndTentacle.RunningTentacle.InstanceName}"); +// +// try +// { +// var logFileText = Policy +// .Handle() +// .WaitAndRetry( +// 20, +// i => TimeSpan.FromMilliseconds(100 * i), +// (exception, _) => { Logger.Information($"Failed to get new log content: {exception.Message}. Retrying!"); }) +// .Execute( +// () => +// { +// var wholeLog = clientAndTentacle.RunningTentacle.ReadAllLogFileText(); +// var newLog = wholeLog.Replace(startingLogText, string.Empty); +// if (string.IsNullOrWhiteSpace(newLog) || !newLog.Contains("CommandLine:")) +// { +// throw new NotLoggedYetException(); +// } +// return newLog; +// }); +// +// logFileText.Should().ContainEquivalentOf($"OperatingSystem: {RuntimeInformation.OSDescription}", "the OSVersion should be in our diagnostics"); +// logFileText.Should().ContainEquivalentOf("OperatingSystem:", "the OSVersion should be in our diagnostics"); +// logFileText.Should().ContainEquivalentOf($"OsBitVersion: {(Environment.Is64BitOperatingSystem ? "x64" : "x86")}", "the OsBitVersion should be in our diagnostics"); +// logFileText.Should().ContainEquivalentOf($"Is64BitProcess: {Environment.Is64BitProcess}", "the Is64BitProcess should be in our diagnostics"); +// +// if (PlatformDetection.IsRunningOnWindows) +// { +// #pragma warning disable CA1416 +// logFileText.Should().ContainEquivalentOf($"CurrentUser: {WindowsIdentity.GetCurrent().Name}", "the CurrentUser should be in our diagnostics"); +// #pragma warning disable CA1416 +// } +// else +// { +// logFileText.Should().ContainEquivalentOf($"CurrentUser: {Environment.UserName}", "the CurrentUser should be in our diagnostics"); +// } +// +// logFileText.Should().ContainEquivalentOf($"MachineName: {Environment.MachineName}", "the MachineName should be in our diagnostics"); +// logFileText.Should().ContainEquivalentOf($"ProcessorCount: {Environment.ProcessorCount}", "the ProcessorCount should be in our diagnostics"); +// logFileText.Should().ContainEquivalentOf($"CurrentDirectory: {Directory.GetCurrentDirectory()}", "the CurrentDirectory should be in our diagnostics"); +// logFileText.Should().ContainEquivalentOf($"TempDirectory: {Path.GetTempPath()}", "the TempDirectory should be in our diagnostics"); +// logFileText.Should().ContainEquivalentOf("HostProcessName: ", "the HostProcessName should be in our diagnostics"); +// stdout.Should().NotContainEquivalentOf($"{RuntimeInformation.OSDescription}", "the OSVersion should not be written to stdout"); +// await Task.CompletedTask; +// } +// catch (NotLoggedYetException) +// { +// Logger.Error("Failed to get new log content"); +// Logger.Error($"Process exit code {exitCode}"); +// Logger.Error($"Starting log text: {Environment.NewLine}{startingLogText}"); +// Logger.Error($"Current log text: {Environment.NewLine}{clientAndTentacle.RunningTentacle.ReadAllLogFileText()}"); +// Logger.Error($"Command StdOut: {Environment.NewLine}{stdout}"); +// Logger.Error($"Command StdErr: {Environment.NewLine}{stderr}"); +// +// throw; +// } +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// public async Task HelpAsFirstArgumentShouldShowCommandSpecificHelp(TentacleConfigurationTestCase tc) +// { +// var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode(tc, null, "help", "version"); +// stderr.Should().BeNullOrEmpty(); +// +// stdout.Should().Be( +// @"Usage: Tentacle version [] +// +// Where [] is any of: +// +// --format=VALUE The format of the output (text,json). Defaults +// to text. +// +// Or one of the common options: +// +// --help Show detailed help for this command +// "); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// // Run these tests in serial to avoid conflicts +// [NonParallelizable] +// public async Task ShowConfigurationCommand(TentacleConfigurationTestCase tc) +// { +// await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); +// await clientAndTentacle.RunningTentacle.Stop(CancellationToken); +// var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( +// tc, +// clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, +// "show-configuration", $"--instance={clientAndTentacle.RunningTentacle.InstanceName}"); +// +// stderr.Should().BeNullOrEmpty(); +// +// // Actually parse and query the document just like our consumer will +// dynamic? settings = JsonConvert.DeserializeObject(stdout); +// +// ((string)settings.Octopus.Home).Should().Be(clientAndTentacle.RunningTentacle.HomeDirectory, "the home directory should match"); +// ((string)settings.Tentacle.Deployment.ApplicationDirectory).Should().Be(clientAndTentacle.RunningTentacle.ApplicationDirectory, "the application directory should match"); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// // Run these tests in serial to avoid conflicts +// [NonParallelizable] +// public async Task ShowConfigurationCommandOnPartiallyConfiguredTentacle(TentacleConfigurationTestCase tc) +// { +// using var homeDirectory = new TemporaryDirectory(); +// var environmentVariables = new Dictionary { { EnvironmentVariables.TentacleMachineConfigurationHomeDirectory, homeDirectory.DirectoryPath } }; +// +// var instanceId = Guid.NewGuid().ToString(); +// using var temporaryDirectory = new TemporaryDirectory(); +// await RunCommandAndAssertExitsWithSuccessExitCode( +// tc, +// environmentVariables, +// "create-instance", $"--instance={instanceId}", "--config", Path.Combine(temporaryDirectory.DirectoryPath, instanceId + ".cfg")); +// +// var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( +// tc, +// environmentVariables, +// "show-configuration", +// $"--instance={instanceId}"); +// +// stderr.Should().BeNullOrEmpty(); +// +// // Actually parse and query the document just like our consumer will +// dynamic? settings = JsonConvert.DeserializeObject(stdout); +// ((string)settings.Octopus.Home).Should().Be(temporaryDirectory.DirectoryPath, "the home directory should match"); +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// // Run these tests in serial to avoid conflicts +// [NonParallelizable] +// public async Task ShowConfigurationCommandLooksSensibleToHumans(TentacleConfigurationTestCase tc) +// { +// await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); +// await clientAndTentacle.RunningTentacle.Stop(CancellationToken); +// var (_, stdout, stderr) = await RunCommandAndAssertExitsWithSuccessExitCode( +// tc, +// clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, +// "show-configuration", $"--instance={clientAndTentacle.RunningTentacle.InstanceName}"); +// +// stderr.Should().BeNullOrEmpty(); +// +// if (tc.TentacleType == TentacleType.Polling) +// { +// stdout.Should().Be($@"{{ +// ""Octopus"": {{ +// ""Home"": ""{JsonFormattedPath(clientAndTentacle.RunningTentacle.HomeDirectory)}"", +// ""Watchdog"": {{ +// ""Enabled"": false, +// ""Instances"": ""*"", +// ""Interval"": 0 +// }} +// }}, +// ""Tentacle"": {{ +// ""CertificateThumbprint"": ""{clientAndTentacle.RunningTentacle.Thumbprint}"", +// ""Communication"": {{ +// ""TrustedOctopusServers"": [ +// {{ +// ""Thumbprint"": ""{clientAndTentacle.Server.Thumbprint}"", +// ""CommunicationStyle"": 2, +// ""Address"": ""https://localhost:{clientAndTentacle.Server.ServerListeningPort}"", +// ""Squid"": null, +// ""SubscriptionId"": ""{clientAndTentacle.RunningTentacle.ServiceUri}"" +// }} +// ] +// }}, +// ""Deployment"": {{ +// ""ApplicationDirectory"": ""{JsonFormattedPath(clientAndTentacle.RunningTentacle.ApplicationDirectory)}"" +// }}, +// ""Services"": {{ +// ""ListenIP"": null, +// ""NoListen"": true, +// ""PortNumber"": 10933 +// }} +// }} +// }} +// "); +// } +// else +// { +// stdout.Should().Be($@"{{ +// ""Octopus"": {{ +// ""Home"": ""{JsonFormattedPath(clientAndTentacle.RunningTentacle.HomeDirectory)}"", +// ""Watchdog"": {{ +// ""Enabled"": false, +// ""Instances"": ""*"", +// ""Interval"": 0 +// }} +// }}, +// ""Tentacle"": {{ +// ""CertificateThumbprint"": ""{clientAndTentacle.RunningTentacle.Thumbprint}"", +// ""Communication"": {{ +// ""TrustedOctopusServers"": [ +// {{ +// ""Thumbprint"": ""{clientAndTentacle.Server.Thumbprint}"", +// ""CommunicationStyle"": 1, +// ""Address"": null, +// ""Squid"": null, +// ""SubscriptionId"": null +// }} +// ] +// }}, +// ""Deployment"": {{ +// ""ApplicationDirectory"": ""{JsonFormattedPath(clientAndTentacle.RunningTentacle.ApplicationDirectory)}"" +// }}, +// ""Services"": {{ +// ""ListenIP"": null, +// ""NoListen"": false, +// ""PortNumber"": {clientAndTentacle.RunningTentacle.ServiceUri.Port} +// }} +// }} +// }} +// "); +// } +// +// await Task.CompletedTask; +// } +// +// [Test] +// [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.None)] +// [WindowsTest] +// // Run these tests in serial to avoid conflicts +// [NonParallelizable] +// public async Task WatchdogCreateAndDeleteCommand(TentacleConfigurationTestCase tc) +// { +// await using var clientAndTentacle = await tc.CreateBuilder().Build(CancellationToken); +// await clientAndTentacle.RunningTentacle.Stop(CancellationToken); +// var create = await RunCommandAndAssertExitsWithSuccessExitCode( +// tc, +// clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, +// "watchdog", "--create", $"--instances={clientAndTentacle.RunningTentacle.InstanceName}"); +// +// create.StdError.Should().BeNullOrEmpty(); +// create.StdOut.Should().ContainEquivalentOf("Creating watchdog task"); +// var delete = await RunCommandAndAssertExitsWithSuccessExitCode( +// tc, +// clientAndTentacle.RunningTentacle.RunTentacleEnvironmentVariables, +// "watchdog", "--delete", $"--instances={clientAndTentacle.RunningTentacle.InstanceName}"); +// +// delete.StdError.Should().BeNullOrEmpty(); +// delete.StdOut.Should().ContainEquivalentOf("Removing watchdog task"); +// } +// +// FileVersionInfo GetVersionInfo(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var tentacleExe = TentacleExeFinder.FindTentacleExe(tentacleConfigurationTestCase.TentacleRuntime); +// +// if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) +// { +// return FileVersionInfo.GetVersionInfo(tentacleExe); +// } +// +// //todo: change this to trust the value set in the context.TentacleExePath (will need a renovation of ExePathResolver to be non windows specific) +// return FileVersionInfo.GetVersionInfo($"{tentacleExe}.dll"); +// } +// +// async Task<(int ExitCode, string StdOut, string StdError)> RunCommandAndAssertExitsWithSuccessExitCode( +// TentacleConfigurationTestCase tentacleConfigurationTestCase, +// IReadOnlyDictionary? environmentVariables, +// params string[] arguments) +// { +// var (exitCode, stdout, stderr) = await RunCommand(tentacleConfigurationTestCase, environmentVariables, arguments); +// exitCode.Should().Be(0, $"we expected the command to succeed.\r\nStdErr: '{stderr}'\r\nStdOut: '{stdout}'"); +// return (exitCode, stdout, stderr); +// } + + async Task<(int ExitCode, string StdOut, string StdError)> RunCommand( + TentacleConfigurationTestCase tentacleConfigurationTestCase, + IReadOnlyDictionary? environmentVariables, + params string[] arguments) + { + using var tempDirectory = new TemporaryDirectory(); + + var environmentVariablesToRunTentacleWith = new Dictionary(); + + if (environmentVariables?.Any() == true) + { + environmentVariablesToRunTentacleWith.AddRange(environmentVariables); + } + + if (!environmentVariablesToRunTentacleWith.ContainsKey(EnvironmentVariables.TentacleMachineConfigurationHomeDirectory)) + { + environmentVariablesToRunTentacleWith.Add(EnvironmentVariables.TentacleMachineConfigurationHomeDirectory, tempDirectory.DirectoryPath); + } + + var tentacleExe = TentacleExeFinder.FindTentacleExe(tentacleConfigurationTestCase.TentacleRuntime); + var output = new StringBuilder(); + var errorOut = new StringBuilder(); + + var result = await RetryHelper.RetryAsync(async () => + { + if (arguments is not null) + { + Logger.Information($"Going to call Cli.Wrap with exe: {tentacleExe}"); + foreach (var argument in arguments) + { + Logger.Information($" ... with argument: {argument}"); + } + Logger.Information($"----"); + } + try + { + var command = Cli.Wrap(tentacleExe) + .WithArguments(arguments) + .WithValidation(CommandResultValidation.None) + .WithStandardOutputPipe(PipeTarget.ToStringBuilder(output)) + .WithStandardErrorPipe(PipeTarget.ToStringBuilder(errorOut)) + .WithEnvironmentVariables(environmentVariablesToRunTentacleWith); + + Logger.Information($"Going to call Cli.Wrap with exe - in try"); + var result = await command.ExecuteAsync(CancellationToken); + Logger.Information($"Finished calling Cli.Wrap with exe - in try"); + return result; + } + catch (Exception e) + { + Logger.Information($"Cli.Wrap(tentacleExe threw an exception:{e.Message}: {e.StackTrace}"); + throw; + } + }); + + return (result.ExitCode, output.ToString(), errorOut.ToString()); + } static string JsonFormattedPath(string path) { diff --git a/source/Octopus.Tentacle.Tests.Integration/TentacleStartupAndShutdownTests.cs b/source/Octopus.Tentacle.Tests.Integration/TentacleStartupAndShutdownTests.cs index 7cd833c4f..9eaa56113 100644 --- a/source/Octopus.Tentacle.Tests.Integration/TentacleStartupAndShutdownTests.cs +++ b/source/Octopus.Tentacle.Tests.Integration/TentacleStartupAndShutdownTests.cs @@ -1,72 +1,72 @@ -#nullable enable -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using FluentAssertions; -using Halibut.Exceptions; -using NUnit.Framework; -using Octopus.Tentacle.Client.Scripts; -using Octopus.Tentacle.CommonTestUtils.Builders; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; -using Octopus.Tentacle.Tests.Integration.Support.TestAttributes; -using Octopus.Tentacle.Tests.Integration.Util.Builders; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class TentacleStartupAndShutdownTests : IntegrationTest - { - [Test] - [TentacleConfigurations] - [RequiresSudoOnLinux] - public async Task WhenRunningTentacleAsAServiceItShouldBeAbleToRestartItself(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - await using (var clientAndTentacle = await tentacleConfigurationTestCase - .CreateBuilder() - .InstallAsAService() - .Build(CancellationToken)) - { - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBodyForCurrentOs( -$@"cd ""{clientAndTentacle.RunningTentacle.TentacleExe.DirectoryName}"" -.\Tentacle.exe service --instance {clientAndTentacle.RunningTentacle.InstanceName} --stop --start", -$@"#!/bin/sh -cd ""{clientAndTentacle.RunningTentacle.TentacleExe.DirectoryName}"" -./Tentacle service --instance {clientAndTentacle.RunningTentacle.InstanceName} --stop --start") - .Build(); - - (Client.Scripts.Models.ScriptExecutionResult ScriptExecutionResult, List ProcessOutput) result; - - try - { - result = await clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - } - catch (ServiceInvocationHalibutClientException ex) - { - Logger.Information(ex, "ServiceInvocationHalibutClientException thrown while Tentacle was restarting itself. This can be ignored for the purpose of this test."); - - // Making Tentacle restart itself can cause internal errors with Script Service - // Execute the script again to get the final result and logs. This will not rerun the script. - result = await clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - } - - result.LogExecuteScriptOutput(Logger); - - result.ProcessOutput.Any(x => x.Text.Contains("Stopping service")).Should().BeTrue("Stopping service should be logged"); - result.ScriptExecutionResult.State.Should().Be(ProcessState.Complete); - - startScriptCommand = new TestExecuteShellScriptCommandBuilder() - .SetScriptBody(new ScriptBuilder().Print("Running...")) - .Build(); - - result = await clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); - result.ProcessOutput.Any(x => x.Text.Contains("Running...")).Should().BeTrue("Running... should be logged"); - result.ScriptExecutionResult.ExitCode.Should().Be(0); - result.ScriptExecutionResult.State.Should().Be(ProcessState.Complete); - } - } - } -} +// #nullable enable +// using System.Collections.Generic; +// using System.Linq; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Halibut.Exceptions; +// using NUnit.Framework; +// using Octopus.Tentacle.Client.Scripts; +// using Octopus.Tentacle.CommonTestUtils.Builders; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; +// using Octopus.Tentacle.Tests.Integration.Support.TestAttributes; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class TentacleStartupAndShutdownTests : IntegrationTest +// { +// [Test] +// [TentacleConfigurations] +// [RequiresSudoOnLinux] +// public async Task WhenRunningTentacleAsAServiceItShouldBeAbleToRestartItself(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// await using (var clientAndTentacle = await tentacleConfigurationTestCase +// .CreateBuilder() +// .InstallAsAService() +// .Build(CancellationToken)) +// { +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBodyForCurrentOs( +// $@"cd ""{clientAndTentacle.RunningTentacle.TentacleExe.DirectoryName}"" +// .\Tentacle.exe service --instance {clientAndTentacle.RunningTentacle.InstanceName} --stop --start", +// $@"#!/bin/sh +// cd ""{clientAndTentacle.RunningTentacle.TentacleExe.DirectoryName}"" +// ./Tentacle service --instance {clientAndTentacle.RunningTentacle.InstanceName} --stop --start") +// .Build(); +// +// (Client.Scripts.Models.ScriptExecutionResult ScriptExecutionResult, List ProcessOutput) result; +// +// try +// { +// result = await clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// } +// catch (ServiceInvocationHalibutClientException ex) +// { +// Logger.Information(ex, "ServiceInvocationHalibutClientException thrown while Tentacle was restarting itself. This can be ignored for the purpose of this test."); +// +// // Making Tentacle restart itself can cause internal errors with Script Service +// // Execute the script again to get the final result and logs. This will not rerun the script. +// result = await clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// } +// +// result.LogExecuteScriptOutput(Logger); +// +// result.ProcessOutput.Any(x => x.Text.Contains("Stopping service")).Should().BeTrue("Stopping service should be logged"); +// result.ScriptExecutionResult.State.Should().Be(ProcessState.Complete); +// +// startScriptCommand = new TestExecuteShellScriptCommandBuilder() +// .SetScriptBody(new ScriptBuilder().Print("Running...")) +// .Build(); +// +// result = await clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); +// result.ProcessOutput.Any(x => x.Text.Contains("Running...")).Should().BeTrue("Running... should be logged"); +// result.ScriptExecutionResult.ExitCode.Should().Be(0); +// result.ScriptExecutionResult.State.Should().Be(ProcessState.Complete); +// } +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/Util/OctopusPhysicalFileSystemFixture.cs b/source/Octopus.Tentacle.Tests.Integration/Util/OctopusPhysicalFileSystemFixture.cs index 60b882b05..b54750f69 100644 --- a/source/Octopus.Tentacle.Tests.Integration/Util/OctopusPhysicalFileSystemFixture.cs +++ b/source/Octopus.Tentacle.Tests.Integration/Util/OctopusPhysicalFileSystemFixture.cs @@ -1,71 +1,71 @@ -using System; -using System.IO; -using FluentAssertions; -using NSubstitute; -using NUnit.Framework; -using Octopus.Diagnostics; -using Octopus.Tentacle.Tests.Integration.Support.TestAttributes; -using Octopus.Tentacle.Util; - -namespace Octopus.Tentacle.Tests.Integration.Util -{ - public class OctopusPhysicalFileSystemFixture - { - [Test] - public void EnsureDiskHasEnoughFreeSpaceShouldWorkForStandardPath() - => new OctopusPhysicalFileSystem(Substitute.For()).EnsureDiskHasEnoughFreeSpace(Path.GetTempPath(), 0); - - [Test] - public void EnsureDiskHasEnoughFreeSpaceShouldWorkButNotCheckForUncPaths() - => new OctopusPhysicalFileSystem(Substitute.For()).EnsureDiskHasEnoughFreeSpace(@"\\does\not\exist", long.MaxValue); - - [Test] - public void EnsureDiskHasEnoughFreeSpaceThrowsAndExceptionIfThereIsNotEnoughSpace() - { - Action exec = () => new OctopusPhysicalFileSystem(Substitute.For()).EnsureDiskHasEnoughFreeSpace(Path.GetTempPath(), long.MaxValue); - exec.Should().Throw().WithMessage("*does not have enough free disk space*"); - } - - [Test] - [RequiresSudoOnLinux] - public void DeleteDirectory_WithReadOnlyFiles_ShouldSucceed() - { - // Arrange - var readonlyDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var readonlyFile = Path.Combine(readonlyDir, "test-readonly.txt"); - - Directory.CreateDirectory(readonlyDir); - File.AppendAllText( - readonlyFile, - "Contents of a readonly file" - ); - - File.SetAttributes(readonlyDir, FileAttributes.ReadOnly); - File.SetAttributes(readonlyFile, FileAttributes.ReadOnly); - - try - { - // Act - var actual = new OctopusPhysicalFileSystem(Substitute.For()); - actual.DeleteDirectory(readonlyDir); - - // Assert - new DirectoryInfo(readonlyDir).Exists.Should().BeFalse(); - } - catch - { - // Clean up temp folder if test fails - if (File.Exists(readonlyFile)) - File.SetAttributes(readonlyFile, FileAttributes.Normal); - - if (Directory.Exists(readonlyDir)) - { - File.SetAttributes(readonlyDir, FileAttributes.Normal); - Directory.Delete(readonlyDir, true); - } - - throw; - } - } - } -} +// using System; +// using System.IO; +// using FluentAssertions; +// using NSubstitute; +// using NUnit.Framework; +// using Octopus.Diagnostics; +// using Octopus.Tentacle.Tests.Integration.Support.TestAttributes; +// using Octopus.Tentacle.Util; +// +// namespace Octopus.Tentacle.Tests.Integration.Util +// { +// public class OctopusPhysicalFileSystemFixture +// { +// [Test] +// public void EnsureDiskHasEnoughFreeSpaceShouldWorkForStandardPath() +// => new OctopusPhysicalFileSystem(Substitute.For()).EnsureDiskHasEnoughFreeSpace(Path.GetTempPath(), 0); +// +// [Test] +// public void EnsureDiskHasEnoughFreeSpaceShouldWorkButNotCheckForUncPaths() +// => new OctopusPhysicalFileSystem(Substitute.For()).EnsureDiskHasEnoughFreeSpace(@"\\does\not\exist", long.MaxValue); +// +// [Test] +// public void EnsureDiskHasEnoughFreeSpaceThrowsAndExceptionIfThereIsNotEnoughSpace() +// { +// Action exec = () => new OctopusPhysicalFileSystem(Substitute.For()).EnsureDiskHasEnoughFreeSpace(Path.GetTempPath(), long.MaxValue); +// exec.Should().Throw().WithMessage("*does not have enough free disk space*"); +// } +// +// [Test] +// [RequiresSudoOnLinux] +// public void DeleteDirectory_WithReadOnlyFiles_ShouldSucceed() +// { +// // Arrange +// var readonlyDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); +// var readonlyFile = Path.Combine(readonlyDir, "test-readonly.txt"); +// +// Directory.CreateDirectory(readonlyDir); +// File.AppendAllText( +// readonlyFile, +// "Contents of a readonly file" +// ); +// +// File.SetAttributes(readonlyDir, FileAttributes.ReadOnly); +// File.SetAttributes(readonlyFile, FileAttributes.ReadOnly); +// +// try +// { +// // Act +// var actual = new OctopusPhysicalFileSystem(Substitute.For()); +// actual.DeleteDirectory(readonlyDir); +// +// // Assert +// new DirectoryInfo(readonlyDir).Exists.Should().BeFalse(); +// } +// catch +// { +// // Clean up temp folder if test fails +// if (File.Exists(readonlyFile)) +// File.SetAttributes(readonlyFile, FileAttributes.Normal); +// +// if (Directory.Exists(readonlyDir)) +// { +// File.SetAttributes(readonlyDir, FileAttributes.Normal); +// Directory.Delete(readonlyDir, true); +// } +// +// throw; +// } +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/Util/RunningScriptFixture.cs b/source/Octopus.Tentacle.Tests.Integration/Util/RunningScriptFixture.cs index 1e1c560e3..84180c6e7 100644 --- a/source/Octopus.Tentacle.Tests.Integration/Util/RunningScriptFixture.cs +++ b/source/Octopus.Tentacle.Tests.Integration/Util/RunningScriptFixture.cs @@ -1,185 +1,185 @@ -using System; -using System.IO; -using System.Threading; -using FluentAssertions; -using NSubstitute; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils; -using Octopus.Tentacle.CommonTestUtils.Diagnostics; -using Octopus.Tentacle.Configuration; -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Diagnostics; -using Octopus.Tentacle.Scripts; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Support.TestAttributes; -using Octopus.Tentacle.Util; -using PlatformDetection = Octopus.Tentacle.Util.PlatformDetection; - -namespace Octopus.Tentacle.Tests.Integration.Util -{ - // These tests are flakey on the build server. - // Sometimes powershell just returns -1 when running these scripts. - // That's why every test has a retry attribute. - [TestFixture] - [NonParallelizable] - public class RunningScriptFixture : IntegrationTest - { - TemporaryDirectory temporaryDirectory; - CancellationTokenSource cancellationTokenSource; - string taskId; - IScriptWorkspace workspace; - TestScriptLog scriptLog; - RunningScript runningScript; - - [SetUp] - public void SetUpLocal() - { - string testRootPath; - IShell shell; - - if (PlatformDetection.IsRunningOnWindows) - { - testRootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), $"OctopusTest-{nameof(RunningScriptFixture)}"); - shell = new PowerShell(); - } - else - { - testRootPath = Path.Combine(Path.GetTempPath(), $"OctopusTest-{nameof(RunningScriptFixture)}"); - shell = new Bash(); - } - - temporaryDirectory = new TemporaryDirectory(Substitute.For(), testRootPath); - var homeConfiguration = Substitute.For(); - homeConfiguration.HomeDirectory.Returns(temporaryDirectory.DirectoryPath); - homeConfiguration.ApplicationSpecificHomeDirectory.Returns(temporaryDirectory.DirectoryPath); - var log = new InMemoryLog(); - var workspaceFactory = new ScriptWorkspaceFactory(new OctopusPhysicalFileSystem(log), homeConfiguration, new SensitiveValueMasker()); - taskId = Guid.NewGuid().ToString(); - workspace = workspaceFactory.GetWorkspace(new ScriptTicket(taskId)); - Console.WriteLine($"Working directory: {workspace.WorkingDirectory}"); - scriptLog = new TestScriptLog(); - cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10)); - runningScript = new RunningScript(shell, - workspace, - scriptLog, - taskId, - cancellationTokenSource.Token, - log); - } - - [TearDown] - public void TearDownLocal() - { - cancellationTokenSource.Dispose(); - temporaryDirectory.Dispose(); - } - - [Test] - [Retry(3)] - public void ExitCode_ShouldBeReturned() - { - workspace.BootstrapScript("exit 99"); - runningScript.Execute(); - runningScript.ExitCode.Should().Be(99, "the exit code of the script should be returned"); - } - - [Test] - [Retry(3)] - public void WriteHost_WritesToStdOut_AndIsReturned() - { - workspace.BootstrapScript("echo Hello"); - runningScript.Execute(); - runningScript.ExitCode.Should().Be(0, "the script should have run to completion"); - scriptLog.StdErr.Length.Should().Be(0, "the script shouldn't have written to stderr"); - scriptLog.StdOut.ToString().Should().ContainEquivalentOf("Hello", "the message should have been written to stdout"); - } - - [Test] - [Retry(3)] - [WindowsTest] - public void WriteDebug_DoesNotWriteAnywhere() - { - workspace.BootstrapScript("Write-Debug Hello"); - runningScript.Execute(); - runningScript.ExitCode.Should().Be(0, "the script should have run to completion"); - scriptLog.StdOut.ToString().Should().NotContain("Hello", "the script shouldn't have written to stdout"); - scriptLog.StdErr.ToString().Should().NotContain("Hello", "the script shouldn't have written to stderr"); - } - - [Test] - [Retry(3)] - [WindowsTest] - public void WriteOutput_WritesToStdOut_AndIsReturned() - { - workspace.BootstrapScript("Write-Output Hello"); - runningScript.Execute(); - runningScript.ExitCode.Should().Be(0, "the script should have run to completion"); - scriptLog.StdErr.ToString().Should().NotContain("Hello", "the script shouldn't have written to stderr"); - scriptLog.StdOut.ToString().Should().ContainEquivalentOf("Hello", "the message should have been written to stdout"); - } - - [Test] - [Retry(3)] - public void WriteError_WritesToStdErr_AndIsReturned() - { - workspace.BootstrapScript(PlatformDetection.IsRunningOnWindows ? "Write-Error EpicFail" : "&2 echo EpicFail"); - - runningScript.Execute(); - if (PlatformDetection.IsRunningOnWindows) - runningScript.ExitCode.Should().Be(1, "Write-Error causes the exit code to be 1"); - else - runningScript.ExitCode.Should().Be(2, "&2 echo causes the exit code to be 1"); - - scriptLog.StdOut.ToString().Should().NotContain("EpicFail", "the script shouldn't have written to stdout"); - scriptLog.StdErr.ToString().Should().ContainEquivalentOf("EpicFail", "the message should have been written to stderr"); - } - - [Test] - [Retry(3)] - public void RunAsCurrentUser_ShouldWork() - { - var scriptBody = PlatformDetection.IsRunningOnWindows - ? $"echo {EchoEnvironmentVariable("username")}" - : "whoami"; - workspace.BootstrapScript(scriptBody); - runningScript.Execute(); - runningScript.ExitCode.Should().Be(0, "the script should have run to completion"); - scriptLog.StdErr.Length.Should().Be(0, "the script shouldn't have written to stderr"); - scriptLog.StdOut.ToString().Should().ContainEquivalentOf(TestEnvironmentHelper.EnvironmentUserName); - } - - [Test] - [Retry(5)] - public void CancellationToken_ShouldKillTheProcess() - { - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) - { - var (shell, sleepCommand) = PlatformDetection.IsRunningOnWindows - ? (new PowerShell(), "Start-Sleep -seconds") - : (new Bash() as IShell, "sleep"); - - var script = new RunningScript(shell, - workspace, - scriptLog, - taskId, - cts.Token, - new InMemoryLog()); - - workspace.BootstrapScript($"echo Starting\n{sleepCommand} 30\necho Finito"); - script.Execute(); - runningScript.ExitCode.Should().Be(0, "the script should have been canceled"); - scriptLog.StdErr.ToString().Should().Be("", "the script shouldn't have written to stderr"); - scriptLog.StdOut.ToString().Should().ContainEquivalentOf("Starting", "the starting message should be written to stdout"); - scriptLog.StdOut.ToString().Should().NotContainEquivalentOf("Finito", "the script should have canceled before writing the finish message"); - } - } - - static string EchoEnvironmentVariable(string varName) - { - if (PlatformDetection.IsRunningOnWindows) - return $"$env:{varName}"; - - return $"${varName}"; - } - } -} \ No newline at end of file +// using System; +// using System.IO; +// using System.Threading; +// using FluentAssertions; +// using NSubstitute; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils; +// using Octopus.Tentacle.CommonTestUtils.Diagnostics; +// using Octopus.Tentacle.Configuration; +// using Octopus.Tentacle.Contracts; +// using Octopus.Tentacle.Diagnostics; +// using Octopus.Tentacle.Scripts; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Support.TestAttributes; +// using Octopus.Tentacle.Util; +// using PlatformDetection = Octopus.Tentacle.Util.PlatformDetection; +// +// namespace Octopus.Tentacle.Tests.Integration.Util +// { +// // These tests are flakey on the build server. +// // Sometimes powershell just returns -1 when running these scripts. +// // That's why every test has a retry attribute. +// [TestFixture] +// [NonParallelizable] +// public class RunningScriptFixture : IntegrationTest +// { +// TemporaryDirectory temporaryDirectory; +// CancellationTokenSource cancellationTokenSource; +// string taskId; +// IScriptWorkspace workspace; +// TestScriptLog scriptLog; +// RunningScript runningScript; +// +// [SetUp] +// public void SetUpLocal() +// { +// string testRootPath; +// IShell shell; +// +// if (PlatformDetection.IsRunningOnWindows) +// { +// testRootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), $"OctopusTest-{nameof(RunningScriptFixture)}"); +// shell = new PowerShell(); +// } +// else +// { +// testRootPath = Path.Combine(Path.GetTempPath(), $"OctopusTest-{nameof(RunningScriptFixture)}"); +// shell = new Bash(); +// } +// +// temporaryDirectory = new TemporaryDirectory(Substitute.For(), testRootPath); +// var homeConfiguration = Substitute.For(); +// homeConfiguration.HomeDirectory.Returns(temporaryDirectory.DirectoryPath); +// homeConfiguration.ApplicationSpecificHomeDirectory.Returns(temporaryDirectory.DirectoryPath); +// var log = new InMemoryLog(); +// var workspaceFactory = new ScriptWorkspaceFactory(new OctopusPhysicalFileSystem(log), homeConfiguration, new SensitiveValueMasker()); +// taskId = Guid.NewGuid().ToString(); +// workspace = workspaceFactory.GetWorkspace(new ScriptTicket(taskId)); +// Console.WriteLine($"Working directory: {workspace.WorkingDirectory}"); +// scriptLog = new TestScriptLog(); +// cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10)); +// runningScript = new RunningScript(shell, +// workspace, +// scriptLog, +// taskId, +// cancellationTokenSource.Token, +// log); +// } +// +// [TearDown] +// public void TearDownLocal() +// { +// cancellationTokenSource.Dispose(); +// temporaryDirectory.Dispose(); +// } +// +// [Test] +// [Retry(3)] +// public void ExitCode_ShouldBeReturned() +// { +// workspace.BootstrapScript("exit 99"); +// runningScript.Execute(); +// runningScript.ExitCode.Should().Be(99, "the exit code of the script should be returned"); +// } +// +// [Test] +// [Retry(3)] +// public void WriteHost_WritesToStdOut_AndIsReturned() +// { +// workspace.BootstrapScript("echo Hello"); +// runningScript.Execute(); +// runningScript.ExitCode.Should().Be(0, "the script should have run to completion"); +// scriptLog.StdErr.Length.Should().Be(0, "the script shouldn't have written to stderr"); +// scriptLog.StdOut.ToString().Should().ContainEquivalentOf("Hello", "the message should have been written to stdout"); +// } +// +// [Test] +// [Retry(3)] +// [WindowsTest] +// public void WriteDebug_DoesNotWriteAnywhere() +// { +// workspace.BootstrapScript("Write-Debug Hello"); +// runningScript.Execute(); +// runningScript.ExitCode.Should().Be(0, "the script should have run to completion"); +// scriptLog.StdOut.ToString().Should().NotContain("Hello", "the script shouldn't have written to stdout"); +// scriptLog.StdErr.ToString().Should().NotContain("Hello", "the script shouldn't have written to stderr"); +// } +// +// [Test] +// [Retry(3)] +// [WindowsTest] +// public void WriteOutput_WritesToStdOut_AndIsReturned() +// { +// workspace.BootstrapScript("Write-Output Hello"); +// runningScript.Execute(); +// runningScript.ExitCode.Should().Be(0, "the script should have run to completion"); +// scriptLog.StdErr.ToString().Should().NotContain("Hello", "the script shouldn't have written to stderr"); +// scriptLog.StdOut.ToString().Should().ContainEquivalentOf("Hello", "the message should have been written to stdout"); +// } +// +// [Test] +// [Retry(3)] +// public void WriteError_WritesToStdErr_AndIsReturned() +// { +// workspace.BootstrapScript(PlatformDetection.IsRunningOnWindows ? "Write-Error EpicFail" : "&2 echo EpicFail"); +// +// runningScript.Execute(); +// if (PlatformDetection.IsRunningOnWindows) +// runningScript.ExitCode.Should().Be(1, "Write-Error causes the exit code to be 1"); +// else +// runningScript.ExitCode.Should().Be(2, "&2 echo causes the exit code to be 1"); +// +// scriptLog.StdOut.ToString().Should().NotContain("EpicFail", "the script shouldn't have written to stdout"); +// scriptLog.StdErr.ToString().Should().ContainEquivalentOf("EpicFail", "the message should have been written to stderr"); +// } +// +// [Test] +// [Retry(3)] +// public void RunAsCurrentUser_ShouldWork() +// { +// var scriptBody = PlatformDetection.IsRunningOnWindows +// ? $"echo {EchoEnvironmentVariable("username")}" +// : "whoami"; +// workspace.BootstrapScript(scriptBody); +// runningScript.Execute(); +// runningScript.ExitCode.Should().Be(0, "the script should have run to completion"); +// scriptLog.StdErr.Length.Should().Be(0, "the script shouldn't have written to stderr"); +// scriptLog.StdOut.ToString().Should().ContainEquivalentOf(TestEnvironmentHelper.EnvironmentUserName); +// } +// +// [Test] +// [Retry(5)] +// public void CancellationToken_ShouldKillTheProcess() +// { +// using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) +// { +// var (shell, sleepCommand) = PlatformDetection.IsRunningOnWindows +// ? (new PowerShell(), "Start-Sleep -seconds") +// : (new Bash() as IShell, "sleep"); +// +// var script = new RunningScript(shell, +// workspace, +// scriptLog, +// taskId, +// cts.Token, +// new InMemoryLog()); +// +// workspace.BootstrapScript($"echo Starting\n{sleepCommand} 30\necho Finito"); +// script.Execute(); +// runningScript.ExitCode.Should().Be(0, "the script should have been canceled"); +// scriptLog.StdErr.ToString().Should().Be("", "the script shouldn't have written to stderr"); +// scriptLog.StdOut.ToString().Should().ContainEquivalentOf("Starting", "the starting message should be written to stdout"); +// scriptLog.StdOut.ToString().Should().NotContainEquivalentOf("Finito", "the script should have canceled before writing the finish message"); +// } +// } +// +// static string EchoEnvironmentVariable(string varName) +// { +// if (PlatformDetection.IsRunningOnWindows) +// return $"$env:{varName}"; +// +// return $"${varName}"; +// } +// } +// } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/Util/SilentProcessRunnerFixture.cs b/source/Octopus.Tentacle.Tests.Integration/Util/SilentProcessRunnerFixture.cs index 8169263a4..858e632fe 100644 --- a/source/Octopus.Tentacle.Tests.Integration/Util/SilentProcessRunnerFixture.cs +++ b/source/Octopus.Tentacle.Tests.Integration/Util/SilentProcessRunnerFixture.cs @@ -1,257 +1,257 @@ -using System; -using System.Text; -using System.Threading; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Support.TestAttributes; -using Octopus.Tentacle.Util; - -namespace Octopus.Tentacle.Tests.Integration.Util -{ - [TestFixture] - public class SilentProcessRunnerFixture : IntegrationTest - { - const int SIG_TERM = 143; - const int SIG_KILL = 137; - string command; - string commandParam; - - [SetUp] - public void SetUpLocal() - { - if (PlatformDetection.IsRunningOnWindows) - { - command = "cmd.exe"; - commandParam = "/c"; - } - else - { - command = "bash"; - commandParam = "-c"; - } - } - - [Test] - public void ExitCode_ShouldBeReturned() - { - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) - { - var arguments = $"{commandParam} \"exit 99\""; - var workingDirectory = ""; - - var exitCode = Execute(command, - arguments, - workingDirectory, - out var debugMessages, - out var infoMessages, - out var errorMessages, - cts.Token); - - exitCode.Should().Be(99, "our custom exit code should be reflected"); - debugMessages.ToString().Should().ContainEquivalentOf($"Starting {command} in working directory '' using '{SilentProcessRunner.EncodingDetector.GetOEMEncoding().EncodingName}' encoding running as '{TestEnvironmentHelper.CurrentUserName}'"); - errorMessages.ToString().Should().BeEmpty("no messages should be written to stderr"); - infoMessages.ToString().Should().BeEmpty("no messages should be written to stdout"); - } - } - - [Test] - public void DebugLogging_ShouldContainDiagnosticsInfo_ForDefault() - { - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) - { - var arguments = $"{commandParam} \"echo hello\""; - var workingDirectory = ""; - - var exitCode = Execute(command, - arguments, - workingDirectory, - out var debugMessages, - out var infoMessages, - out var errorMessages, - cts.Token); - - exitCode.Should().Be(0, "the process should have run to completion"); - debugMessages.ToString() - .Should() - .ContainEquivalentOf(command, "the command should be logged") - .And.ContainEquivalentOf(TestEnvironmentHelper.CurrentUserName, "the current user details should be logged"); - infoMessages.ToString().Should().ContainEquivalentOf("hello"); - errorMessages.ToString().Should().BeEmpty("no messages should be written to stderr"); - } - } - - [Test] - [Retry(3)] - public void CancellationToken_ShouldForceKillTheProcess() - { - // Terminate the process after a very short time so the test doesn't run forever - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1))) - { - // Starting a new instance of cmd.exe will run indefinitely waiting for user input - var arguments = ""; - var workingDirectory = ""; - - var exitCode = Execute(command, - arguments, - workingDirectory, - out var debugMessages, - out var infoMessages, - out var errorMessages, - cts.Token); - - if (PlatformDetection.IsRunningOnWindows) - { - exitCode.Should().BeLessOrEqualTo(0, "the process should have been terminated"); - infoMessages.ToString().Should().ContainEquivalentOf("Microsoft Windows", "the default command-line header would be written to stdout"); - } - else - { - exitCode.Should().BeOneOf(SIG_KILL, SIG_TERM, 0); - } - - errorMessages.ToString().Should().BeEmpty("no messages should be written to stderr"); - } - } - - [Test] - public void EchoHello_ShouldWriteToStdOut() - { - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) - { - var arguments = $"{commandParam} \"echo hello\""; - var workingDirectory = ""; - - var exitCode = Execute(command, - arguments, - workingDirectory, - out var debugMessages, - out var infoMessages, - out var errorMessages, - cts.Token); - - exitCode.Should().Be(0, "the process should have run to completion"); - errorMessages.ToString().Should().BeEmpty("no messages should be written to stderr"); - infoMessages.ToString().Should().ContainEquivalentOf("hello"); - } - } - - [Test] - public void EchoError_ShouldWriteToStdErr() - { - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) - { - var arguments = $"{commandParam} \"echo Something went wrong! 1>&2\""; - var workingDirectory = ""; - - var exitCode = Execute(command, - arguments, - workingDirectory, - out var debugMessages, - out var infoMessages, - out var errorMessages, - cts.Token); - - exitCode.Should().Be(0, "the process should have run to completion"); - infoMessages.ToString().Should().BeEmpty("no messages should be written to stdout"); - errorMessages.ToString().Should().ContainEquivalentOf("Something went wrong!"); - } - } - - [Test] - public void RunAsCurrentUser_ShouldWork() - { - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) - { - var arguments = PlatformDetection.IsRunningOnWindows - ? $"{commandParam} \"echo {EchoEnvironmentVariable("username")}\"" - : $"{commandParam} \"whoami\""; - var workingDirectory = ""; - - var exitCode = Execute(command, - arguments, - workingDirectory, - out var debugMessages, - out var infoMessages, - out var errorMessages, - cts.Token); - - exitCode.Should().Be(0, "the process should have run to completion"); - errorMessages.ToString().Should().BeEmpty("no messages should be written to stderr"); - infoMessages.ToString().Should().ContainEquivalentOf($@"{TestEnvironmentHelper.EnvironmentUserName}"); - } - } - - [Test] - [WindowsTest] - [TestCase("powershell.exe", "-command \"Write-Host $env:userdomain\\$env:username\"")] - public void RunAsCurrentUser_PowerShell_ShouldWork(string command, string arguments) - { - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) - { - var workingDirectory = ""; - - var exitCode = Execute(command, - arguments, - workingDirectory, - out var debugMessages, - out var infoMessages, - out var errorMessages, - cts.Token); - - exitCode.Should().Be(0, "the process should have run to completion"); - errorMessages.ToString().Should().BeEmpty("no messages should be written to stderr"); - - infoMessages.ToString().Should().ContainEquivalentOf($@"{TestEnvironmentHelper.EnvironmentDomain}\{TestEnvironmentHelper.EnvironmentUserName}"); - } - } - - static string EchoEnvironmentVariable(string varName) - { - if (PlatformDetection.IsRunningOnWindows) - return $"%{varName}%"; - - return $"${varName}"; - } - - static int Execute( - string command, - string arguments, - string workingDirectory, - out StringBuilder debugMessages, - out StringBuilder infoMessages, - out StringBuilder errorMessages, - CancellationToken cancel) - { - var debug = new StringBuilder(); - var info = new StringBuilder(); - var error = new StringBuilder(); - var exitCode = SilentProcessRunner.ExecuteCommand( - command, - arguments, - workingDirectory, - x => - { - Console.WriteLine($"{DateTime.UtcNow} DBG: {x}"); - debug.Append(x); - }, - x => - { - Console.WriteLine($"{DateTime.UtcNow} INF: {x}"); - info.Append(x); - }, - x => - { - Console.WriteLine($"{DateTime.UtcNow} ERR: {x}"); - error.Append(x); - }, - cancel); - - debugMessages = debug; - infoMessages = info; - errorMessages = error; - - return exitCode; - } - } -} +// using System; +// using System.Text; +// using System.Threading; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Support.TestAttributes; +// using Octopus.Tentacle.Util; +// +// namespace Octopus.Tentacle.Tests.Integration.Util +// { +// [TestFixture] +// public class SilentProcessRunnerFixture : IntegrationTest +// { +// const int SIG_TERM = 143; +// const int SIG_KILL = 137; +// string command; +// string commandParam; +// +// [SetUp] +// public void SetUpLocal() +// { +// if (PlatformDetection.IsRunningOnWindows) +// { +// command = "cmd.exe"; +// commandParam = "/c"; +// } +// else +// { +// command = "bash"; +// commandParam = "-c"; +// } +// } +// +// [Test] +// public void ExitCode_ShouldBeReturned() +// { +// using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) +// { +// var arguments = $"{commandParam} \"exit 99\""; +// var workingDirectory = ""; +// +// var exitCode = Execute(command, +// arguments, +// workingDirectory, +// out var debugMessages, +// out var infoMessages, +// out var errorMessages, +// cts.Token); +// +// exitCode.Should().Be(99, "our custom exit code should be reflected"); +// debugMessages.ToString().Should().ContainEquivalentOf($"Starting {command} in working directory '' using '{SilentProcessRunner.EncodingDetector.GetOEMEncoding().EncodingName}' encoding running as '{TestEnvironmentHelper.CurrentUserName}'"); +// errorMessages.ToString().Should().BeEmpty("no messages should be written to stderr"); +// infoMessages.ToString().Should().BeEmpty("no messages should be written to stdout"); +// } +// } +// +// [Test] +// public void DebugLogging_ShouldContainDiagnosticsInfo_ForDefault() +// { +// using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) +// { +// var arguments = $"{commandParam} \"echo hello\""; +// var workingDirectory = ""; +// +// var exitCode = Execute(command, +// arguments, +// workingDirectory, +// out var debugMessages, +// out var infoMessages, +// out var errorMessages, +// cts.Token); +// +// exitCode.Should().Be(0, "the process should have run to completion"); +// debugMessages.ToString() +// .Should() +// .ContainEquivalentOf(command, "the command should be logged") +// .And.ContainEquivalentOf(TestEnvironmentHelper.CurrentUserName, "the current user details should be logged"); +// infoMessages.ToString().Should().ContainEquivalentOf("hello"); +// errorMessages.ToString().Should().BeEmpty("no messages should be written to stderr"); +// } +// } +// +// [Test] +// [Retry(3)] +// public void CancellationToken_ShouldForceKillTheProcess() +// { +// // Terminate the process after a very short time so the test doesn't run forever +// using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1))) +// { +// // Starting a new instance of cmd.exe will run indefinitely waiting for user input +// var arguments = ""; +// var workingDirectory = ""; +// +// var exitCode = Execute(command, +// arguments, +// workingDirectory, +// out var debugMessages, +// out var infoMessages, +// out var errorMessages, +// cts.Token); +// +// if (PlatformDetection.IsRunningOnWindows) +// { +// exitCode.Should().BeLessOrEqualTo(0, "the process should have been terminated"); +// infoMessages.ToString().Should().ContainEquivalentOf("Microsoft Windows", "the default command-line header would be written to stdout"); +// } +// else +// { +// exitCode.Should().BeOneOf(SIG_KILL, SIG_TERM, 0); +// } +// +// errorMessages.ToString().Should().BeEmpty("no messages should be written to stderr"); +// } +// } +// +// [Test] +// public void EchoHello_ShouldWriteToStdOut() +// { +// using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) +// { +// var arguments = $"{commandParam} \"echo hello\""; +// var workingDirectory = ""; +// +// var exitCode = Execute(command, +// arguments, +// workingDirectory, +// out var debugMessages, +// out var infoMessages, +// out var errorMessages, +// cts.Token); +// +// exitCode.Should().Be(0, "the process should have run to completion"); +// errorMessages.ToString().Should().BeEmpty("no messages should be written to stderr"); +// infoMessages.ToString().Should().ContainEquivalentOf("hello"); +// } +// } +// +// [Test] +// public void EchoError_ShouldWriteToStdErr() +// { +// using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) +// { +// var arguments = $"{commandParam} \"echo Something went wrong! 1>&2\""; +// var workingDirectory = ""; +// +// var exitCode = Execute(command, +// arguments, +// workingDirectory, +// out var debugMessages, +// out var infoMessages, +// out var errorMessages, +// cts.Token); +// +// exitCode.Should().Be(0, "the process should have run to completion"); +// infoMessages.ToString().Should().BeEmpty("no messages should be written to stdout"); +// errorMessages.ToString().Should().ContainEquivalentOf("Something went wrong!"); +// } +// } +// +// [Test] +// public void RunAsCurrentUser_ShouldWork() +// { +// using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) +// { +// var arguments = PlatformDetection.IsRunningOnWindows +// ? $"{commandParam} \"echo {EchoEnvironmentVariable("username")}\"" +// : $"{commandParam} \"whoami\""; +// var workingDirectory = ""; +// +// var exitCode = Execute(command, +// arguments, +// workingDirectory, +// out var debugMessages, +// out var infoMessages, +// out var errorMessages, +// cts.Token); +// +// exitCode.Should().Be(0, "the process should have run to completion"); +// errorMessages.ToString().Should().BeEmpty("no messages should be written to stderr"); +// infoMessages.ToString().Should().ContainEquivalentOf($@"{TestEnvironmentHelper.EnvironmentUserName}"); +// } +// } +// +// [Test] +// [WindowsTest] +// [TestCase("powershell.exe", "-command \"Write-Host $env:userdomain\\$env:username\"")] +// public void RunAsCurrentUser_PowerShell_ShouldWork(string command, string arguments) +// { +// using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) +// { +// var workingDirectory = ""; +// +// var exitCode = Execute(command, +// arguments, +// workingDirectory, +// out var debugMessages, +// out var infoMessages, +// out var errorMessages, +// cts.Token); +// +// exitCode.Should().Be(0, "the process should have run to completion"); +// errorMessages.ToString().Should().BeEmpty("no messages should be written to stderr"); +// +// infoMessages.ToString().Should().ContainEquivalentOf($@"{TestEnvironmentHelper.EnvironmentDomain}\{TestEnvironmentHelper.EnvironmentUserName}"); +// } +// } +// +// static string EchoEnvironmentVariable(string varName) +// { +// if (PlatformDetection.IsRunningOnWindows) +// return $"%{varName}%"; +// +// return $"${varName}"; +// } +// +// static int Execute( +// string command, +// string arguments, +// string workingDirectory, +// out StringBuilder debugMessages, +// out StringBuilder infoMessages, +// out StringBuilder errorMessages, +// CancellationToken cancel) +// { +// var debug = new StringBuilder(); +// var info = new StringBuilder(); +// var error = new StringBuilder(); +// var exitCode = SilentProcessRunner.ExecuteCommand( +// command, +// arguments, +// workingDirectory, +// x => +// { +// Console.WriteLine($"{DateTime.UtcNow} DBG: {x}"); +// debug.Append(x); +// }, +// x => +// { +// Console.WriteLine($"{DateTime.UtcNow} INF: {x}"); +// info.Append(x); +// }, +// x => +// { +// Console.WriteLine($"{DateTime.UtcNow} ERR: {x}"); +// error.Append(x); +// }, +// cancel); +// +// debugMessages = debug; +// infoMessages = info; +// errorMessages = error; +// +// return exitCode; +// } +// } +// } diff --git a/source/Octopus.Tentacle.Tests.Integration/WorkspaceCleanerTests.cs b/source/Octopus.Tentacle.Tests.Integration/WorkspaceCleanerTests.cs index 8eb83f876..378f1e2e9 100644 --- a/source/Octopus.Tentacle.Tests.Integration/WorkspaceCleanerTests.cs +++ b/source/Octopus.Tentacle.Tests.Integration/WorkspaceCleanerTests.cs @@ -1,257 +1,257 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; -using Octopus.Tentacle.CommonTestUtils; -using Octopus.Tentacle.CommonTestUtils.Diagnostics; -using Octopus.Tentacle.Scripts; -using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; -using Octopus.Tentacle.Tests.Integration.Support; -using Octopus.Tentacle.Tests.Integration.Util; -using Octopus.Tentacle.Tests.Integration.Util.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; - -namespace Octopus.Tentacle.Tests.Integration -{ - [IntegrationTestTimeout] - public class WorkspaceCleanerTests : IntegrationTest - { - [Test] - [TentacleConfigurations(testDefaultTentacleRuntimeOnly: true)] - public async Task WhenScriptServiceIsRunningAndWritesLogFile_ThenWorkspaceIsNotDeleted(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var cleanerDelay = TimeSpan.FromMilliseconds(500); - var deleteWorkspacesOlderThan = TimeSpan.FromMilliseconds(500); - - var existingHomeDirectory = new TemporaryDirectory(); - - var waitBeforeCompletingScriptFile = Path.Combine(existingHomeDirectory.DirectoryPath, "WaitForMeToExist.txt"); - var startScriptCommand = new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.WaitForFileToExist(waitBeforeCompletingScriptFile)).Build(); - var startScriptWorkspaceDirectory = GetWorkspaceDirectoryPath(existingHomeDirectory.DirectoryPath, startScriptCommand.ScriptTicket.TaskId); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacle(b => - { - b.WithHomeDirectory(existingHomeDirectory) - .WithWorkspaceCleaningSettings(cleanerDelay, deleteWorkspacesOlderThan); - }) - .Build(CancellationToken); - - // Start task - var runningScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, new InMemoryLog()); - await Wait.For(() => Directory.Exists(startScriptWorkspaceDirectory), - TimeSpan.FromSeconds(60), - () => throw new Exception("Workspace directory did not get created"), - CancellationToken); - - // Ensure Workspace Cleaning Has Run - var existingWorkspaceDirectory = GivenExistingWorkspaceExists(existingHomeDirectory); - await File.WriteAllTextAsync(ScriptWorkspace.GetLogFilePath(existingWorkspaceDirectory), "Existing log file"); - await Wait.For(() => !Directory.Exists(existingWorkspaceDirectory), - TimeSpan.FromSeconds(60), - () => throw new Exception("Workspace directory did not get deleted"), - CancellationToken); - - Directory.Exists(startScriptWorkspaceDirectory).Should().BeTrue("Workspace should not have been cleaned up"); - - await File.WriteAllTextAsync(waitBeforeCompletingScriptFile, "Write file that makes script continue executing"); - await runningScriptTask; - } - - [Test] - [TentacleConfigurations(testDefaultTentacleRuntimeOnly: true)] - public async Task WhenScriptServiceIsRunningAndWritesBootstrapScript_ThenWorkspaceIsNotDeleted(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var cleanerDelay = TimeSpan.FromMilliseconds(500); - var deleteWorkspacesOlderThan = TimeSpan.FromMilliseconds(500); - - var existingHomeDirectory = new TemporaryDirectory(); - - var waitBeforeCompletingScriptFile = Path.Combine(existingHomeDirectory.DirectoryPath, "WaitForMeToExist.txt"); - var startScriptCommand = new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.WaitForFileToExist(waitBeforeCompletingScriptFile)).Build(); - var startScriptWorkspaceDirectory = GetWorkspaceDirectoryPath(existingHomeDirectory.DirectoryPath, startScriptCommand.ScriptTicket.TaskId); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacle(b => - { - b.WithHomeDirectory(existingHomeDirectory) - .WithWorkspaceCleaningSettings(cleanerDelay, deleteWorkspacesOlderThan); - }) - .Build(CancellationToken); - - // Start task - var runningScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, new InMemoryLog()); - await Wait.For(() => Directory.Exists(startScriptWorkspaceDirectory), - TimeSpan.FromSeconds(60), - () => throw new Exception("Workspace directory did not get created"), - CancellationToken); - - // Ensure Workspace Cleaning Has Run - var existingWorkspaceDirectory = GivenExistingWorkspaceExists(existingHomeDirectory); - await File.WriteAllTextAsync(GetBootstrapScriptFilePath(existingWorkspaceDirectory), "Existing bootstrap file"); - await Wait.For(() => !Directory.Exists(existingWorkspaceDirectory), - TimeSpan.FromSeconds(60), - () => throw new Exception("Workspace directory did not get deleted"), - CancellationToken); - - Directory.Exists(startScriptWorkspaceDirectory).Should().BeTrue("Workspace should not have been cleaned up"); - - await File.WriteAllTextAsync(waitBeforeCompletingScriptFile, "Write file that makes script continue executing"); - await runningScriptTask; - } - - [Test] - [TentacleConfigurations(testDefaultTentacleRuntimeOnly: true)] - public async Task WhenCompleteScriptIsNotCalled_ThenWorkspaceShouldGetDeletedWhenScriptFinishesRunning(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var cleanerDelay = TimeSpan.FromMilliseconds(500); - var deleteWorkspacesOlderThan = TimeSpan.FromMilliseconds(500); - - var startScriptCommand = new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.Print("Hello")).Build(); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacle(b => - { - b.WithWorkspaceCleaningSettings(cleanerDelay, deleteWorkspacesOlderThan); - }) - .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() - .DecorateAllScriptServicesWith(u => u - .BeforeCompleteScript( - () => throw new NotImplementedException("Force failure to simulate tentacle client crashing, and ensure we do not complete the script"))) - .Build()) - .Build(CancellationToken); - - await AssertionExtensions - .Should(() => clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, new InMemoryLog())) - .ThrowAsync(); - - var workspaceDirectory = GetWorkspaceDirectoryPath(clientAndTentacle.RunningTentacle.HomeDirectory, startScriptCommand.ScriptTicket.TaskId); - - await Wait.For(() => !Directory.Exists(workspaceDirectory), - TimeSpan.FromSeconds(20), - () => - { - try - { - Directory.Delete(workspaceDirectory, true); - } - catch (Exception) - { - // Deleting a worksapce is best effort and can silently fail if it is in use / locked by something. - // If the cleaner failed to delete the directory and we cannot delete it in the test we can assume that it - // is a valid failure and the test was successful. - - return; - } - - throw new Exception("Workspace directory did not get deleted by the workspace cleaner but the test was able to delete it. This indicates there is an issue in the Tentacle code."); - }, - CancellationToken); - } - - [Test] - [TentacleConfigurations(testDefaultTentacleRuntimeOnly: true)] - public async Task WhenTentacleStarts_WithWorkspacesOlderThanThreshold_ThenWorkspaceWithLogFileIsDeleted(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var cleanerDelay = TimeSpan.FromMilliseconds(500); - var deleteWorkspacesOlderThan = TimeSpan.FromMilliseconds(500); - - var existingHomeDirectory = new TemporaryDirectory(); - - var existingWorkspaceDirectoryWithoutLogFile = GivenExistingWorkspaceExists(existingHomeDirectory); - var existingWorkspaceDirectoryWithLogFile = GivenExistingWorkspaceExists(existingHomeDirectory); - await File.WriteAllTextAsync(ScriptWorkspace.GetLogFilePath(existingWorkspaceDirectoryWithLogFile), "Existing log file"); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacle(b => - { - b.WithHomeDirectory(existingHomeDirectory) - .WithWorkspaceCleaningSettings(cleanerDelay, deleteWorkspacesOlderThan); - }) - .Build(CancellationToken); - - await Wait.For(() => !Directory.Exists(existingWorkspaceDirectoryWithLogFile), - TimeSpan.FromSeconds(60), - () => throw new Exception("Workspace directory did not get deleted"), - CancellationToken); - Directory.Exists(existingWorkspaceDirectoryWithoutLogFile).Should().BeTrue(); - } - - [Test] - [TentacleConfigurations(testDefaultTentacleRuntimeOnly: true)] - public async Task WhenTentacleStarts_WithWorkspacesOlderThanThreshold_ThenWorkspaceWithBootstrapFileIsDeleted(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var cleanerDelay = TimeSpan.FromMilliseconds(500); - var deleteWorkspacesOlderThan = TimeSpan.FromMilliseconds(500); - - var existingHomeDirectory = new TemporaryDirectory(); - - var existingWorkspaceDirectoryWithoutLogFile = GivenExistingWorkspaceExists(existingHomeDirectory); - var existingWorkspaceDirectoryWithLogFile = GivenExistingWorkspaceExists(existingHomeDirectory); - await File.WriteAllTextAsync(GetBootstrapScriptFilePath(existingWorkspaceDirectoryWithLogFile), "Existing bootstrap file"); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacle(b => - { - b.WithHomeDirectory(existingHomeDirectory) - .WithWorkspaceCleaningSettings(cleanerDelay, deleteWorkspacesOlderThan); - }) - .Build(CancellationToken); - - await Wait.For(() => !Directory.Exists(existingWorkspaceDirectoryWithLogFile), - TimeSpan.FromSeconds(60), - () => throw new Exception("Workspace directory did not get deleted"), - CancellationToken); - Directory.Exists(existingWorkspaceDirectoryWithoutLogFile).Should().BeTrue(); - } - - [Test] - [TentacleConfigurations(testDefaultTentacleRuntimeOnly: true)] - public async Task WhenTentacleStarts_WithWorkspaceYoungerThanThreshold_ThenWorkspaceIsLeftAlone(TentacleConfigurationTestCase tentacleConfigurationTestCase) - { - var cleanerDelay = TimeSpan.FromMilliseconds(500); - var deleteWorkspacesOlderThan = TimeSpan.FromMinutes(30); - - var existingHomeDirectory = new TemporaryDirectory(); - - var existingWorkspaceDirectory = GivenExistingWorkspaceExists(existingHomeDirectory); - await File.WriteAllTextAsync(ScriptWorkspace.GetLogFilePath(existingWorkspaceDirectory), "Existing log file"); - - await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() - .WithTentacle(b => - { - b.WithHomeDirectory(existingHomeDirectory) - .WithWorkspaceCleaningSettings(cleanerDelay, deleteWorkspacesOlderThan); - }) - .Build(CancellationToken); - - await Task.Delay(1000, CancellationToken); - - Directory.Exists(existingWorkspaceDirectory).Should().BeTrue(); - } - - static string GetWorkspaceDirectoryPath(string homeDirectory, string scriptTicket) - { - var workspaceDirectory = Path.Combine( - homeDirectory, - ScriptWorkspaceFactory.WorkDirectory, - scriptTicket); - return workspaceDirectory; - } - - static string GivenExistingWorkspaceExists(TemporaryDirectory existingHomeDirectory) - { - var existingWorkspaceDirectory = GetWorkspaceDirectoryPath(existingHomeDirectory.DirectoryPath, Guid.NewGuid().ToString()); - Directory.CreateDirectory(existingWorkspaceDirectory); - return existingWorkspaceDirectory; - } - - static string GetBootstrapScriptFilePath(string workingDirectory) - { - return !PlatformDetection.IsRunningOnWindows - ? BashScriptWorkspace.GetBashBootstrapScriptFilePath(workingDirectory) - : ScriptWorkspace.GetBootstrapScriptFilePath(workingDirectory); - } - } -} \ No newline at end of file +// using System; +// using System.IO; +// using System.Threading.Tasks; +// using FluentAssertions; +// using NUnit.Framework; +// using Octopus.Tentacle.CommonTestUtils; +// using Octopus.Tentacle.CommonTestUtils.Diagnostics; +// using Octopus.Tentacle.Scripts; +// using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators; +// using Octopus.Tentacle.Tests.Integration.Support; +// using Octopus.Tentacle.Tests.Integration.Util; +// using Octopus.Tentacle.Tests.Integration.Util.Builders; +// using Octopus.Tentacle.Tests.Integration.Util.Builders.Decorators; +// +// namespace Octopus.Tentacle.Tests.Integration +// { +// [IntegrationTestTimeout] +// public class WorkspaceCleanerTests : IntegrationTest +// { +// [Test] +// [TentacleConfigurations(testDefaultTentacleRuntimeOnly: true)] +// public async Task WhenScriptServiceIsRunningAndWritesLogFile_ThenWorkspaceIsNotDeleted(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var cleanerDelay = TimeSpan.FromMilliseconds(500); +// var deleteWorkspacesOlderThan = TimeSpan.FromMilliseconds(500); +// +// var existingHomeDirectory = new TemporaryDirectory(); +// +// var waitBeforeCompletingScriptFile = Path.Combine(existingHomeDirectory.DirectoryPath, "WaitForMeToExist.txt"); +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.WaitForFileToExist(waitBeforeCompletingScriptFile)).Build(); +// var startScriptWorkspaceDirectory = GetWorkspaceDirectoryPath(existingHomeDirectory.DirectoryPath, startScriptCommand.ScriptTicket.TaskId); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacle(b => +// { +// b.WithHomeDirectory(existingHomeDirectory) +// .WithWorkspaceCleaningSettings(cleanerDelay, deleteWorkspacesOlderThan); +// }) +// .Build(CancellationToken); +// +// // Start task +// var runningScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, new InMemoryLog()); +// await Wait.For(() => Directory.Exists(startScriptWorkspaceDirectory), +// TimeSpan.FromSeconds(60), +// () => throw new Exception("Workspace directory did not get created"), +// CancellationToken); +// +// // Ensure Workspace Cleaning Has Run +// var existingWorkspaceDirectory = GivenExistingWorkspaceExists(existingHomeDirectory); +// await File.WriteAllTextAsync(ScriptWorkspace.GetLogFilePath(existingWorkspaceDirectory), "Existing log file"); +// await Wait.For(() => !Directory.Exists(existingWorkspaceDirectory), +// TimeSpan.FromSeconds(60), +// () => throw new Exception("Workspace directory did not get deleted"), +// CancellationToken); +// +// Directory.Exists(startScriptWorkspaceDirectory).Should().BeTrue("Workspace should not have been cleaned up"); +// +// await File.WriteAllTextAsync(waitBeforeCompletingScriptFile, "Write file that makes script continue executing"); +// await runningScriptTask; +// } +// +// [Test] +// [TentacleConfigurations(testDefaultTentacleRuntimeOnly: true)] +// public async Task WhenScriptServiceIsRunningAndWritesBootstrapScript_ThenWorkspaceIsNotDeleted(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var cleanerDelay = TimeSpan.FromMilliseconds(500); +// var deleteWorkspacesOlderThan = TimeSpan.FromMilliseconds(500); +// +// var existingHomeDirectory = new TemporaryDirectory(); +// +// var waitBeforeCompletingScriptFile = Path.Combine(existingHomeDirectory.DirectoryPath, "WaitForMeToExist.txt"); +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.WaitForFileToExist(waitBeforeCompletingScriptFile)).Build(); +// var startScriptWorkspaceDirectory = GetWorkspaceDirectoryPath(existingHomeDirectory.DirectoryPath, startScriptCommand.ScriptTicket.TaskId); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacle(b => +// { +// b.WithHomeDirectory(existingHomeDirectory) +// .WithWorkspaceCleaningSettings(cleanerDelay, deleteWorkspacesOlderThan); +// }) +// .Build(CancellationToken); +// +// // Start task +// var runningScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, new InMemoryLog()); +// await Wait.For(() => Directory.Exists(startScriptWorkspaceDirectory), +// TimeSpan.FromSeconds(60), +// () => throw new Exception("Workspace directory did not get created"), +// CancellationToken); +// +// // Ensure Workspace Cleaning Has Run +// var existingWorkspaceDirectory = GivenExistingWorkspaceExists(existingHomeDirectory); +// await File.WriteAllTextAsync(GetBootstrapScriptFilePath(existingWorkspaceDirectory), "Existing bootstrap file"); +// await Wait.For(() => !Directory.Exists(existingWorkspaceDirectory), +// TimeSpan.FromSeconds(60), +// () => throw new Exception("Workspace directory did not get deleted"), +// CancellationToken); +// +// Directory.Exists(startScriptWorkspaceDirectory).Should().BeTrue("Workspace should not have been cleaned up"); +// +// await File.WriteAllTextAsync(waitBeforeCompletingScriptFile, "Write file that makes script continue executing"); +// await runningScriptTask; +// } +// +// [Test] +// [TentacleConfigurations(testDefaultTentacleRuntimeOnly: true)] +// public async Task WhenCompleteScriptIsNotCalled_ThenWorkspaceShouldGetDeletedWhenScriptFinishesRunning(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var cleanerDelay = TimeSpan.FromMilliseconds(500); +// var deleteWorkspacesOlderThan = TimeSpan.FromMilliseconds(500); +// +// var startScriptCommand = new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.Print("Hello")).Build(); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacle(b => +// { +// b.WithWorkspaceCleaningSettings(cleanerDelay, deleteWorkspacesOlderThan); +// }) +// .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() +// .DecorateAllScriptServicesWith(u => u +// .BeforeCompleteScript( +// () => throw new NotImplementedException("Force failure to simulate tentacle client crashing, and ensure we do not complete the script"))) +// .Build()) +// .Build(CancellationToken); +// +// await AssertionExtensions +// .Should(() => clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, new InMemoryLog())) +// .ThrowAsync(); +// +// var workspaceDirectory = GetWorkspaceDirectoryPath(clientAndTentacle.RunningTentacle.HomeDirectory, startScriptCommand.ScriptTicket.TaskId); +// +// await Wait.For(() => !Directory.Exists(workspaceDirectory), +// TimeSpan.FromSeconds(20), +// () => +// { +// try +// { +// Directory.Delete(workspaceDirectory, true); +// } +// catch (Exception) +// { +// // Deleting a worksapce is best effort and can silently fail if it is in use / locked by something. +// // If the cleaner failed to delete the directory and we cannot delete it in the test we can assume that it +// // is a valid failure and the test was successful. +// +// return; +// } +// +// throw new Exception("Workspace directory did not get deleted by the workspace cleaner but the test was able to delete it. This indicates there is an issue in the Tentacle code."); +// }, +// CancellationToken); +// } +// +// [Test] +// [TentacleConfigurations(testDefaultTentacleRuntimeOnly: true)] +// public async Task WhenTentacleStarts_WithWorkspacesOlderThanThreshold_ThenWorkspaceWithLogFileIsDeleted(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var cleanerDelay = TimeSpan.FromMilliseconds(500); +// var deleteWorkspacesOlderThan = TimeSpan.FromMilliseconds(500); +// +// var existingHomeDirectory = new TemporaryDirectory(); +// +// var existingWorkspaceDirectoryWithoutLogFile = GivenExistingWorkspaceExists(existingHomeDirectory); +// var existingWorkspaceDirectoryWithLogFile = GivenExistingWorkspaceExists(existingHomeDirectory); +// await File.WriteAllTextAsync(ScriptWorkspace.GetLogFilePath(existingWorkspaceDirectoryWithLogFile), "Existing log file"); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacle(b => +// { +// b.WithHomeDirectory(existingHomeDirectory) +// .WithWorkspaceCleaningSettings(cleanerDelay, deleteWorkspacesOlderThan); +// }) +// .Build(CancellationToken); +// +// await Wait.For(() => !Directory.Exists(existingWorkspaceDirectoryWithLogFile), +// TimeSpan.FromSeconds(60), +// () => throw new Exception("Workspace directory did not get deleted"), +// CancellationToken); +// Directory.Exists(existingWorkspaceDirectoryWithoutLogFile).Should().BeTrue(); +// } +// +// [Test] +// [TentacleConfigurations(testDefaultTentacleRuntimeOnly: true)] +// public async Task WhenTentacleStarts_WithWorkspacesOlderThanThreshold_ThenWorkspaceWithBootstrapFileIsDeleted(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var cleanerDelay = TimeSpan.FromMilliseconds(500); +// var deleteWorkspacesOlderThan = TimeSpan.FromMilliseconds(500); +// +// var existingHomeDirectory = new TemporaryDirectory(); +// +// var existingWorkspaceDirectoryWithoutLogFile = GivenExistingWorkspaceExists(existingHomeDirectory); +// var existingWorkspaceDirectoryWithLogFile = GivenExistingWorkspaceExists(existingHomeDirectory); +// await File.WriteAllTextAsync(GetBootstrapScriptFilePath(existingWorkspaceDirectoryWithLogFile), "Existing bootstrap file"); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacle(b => +// { +// b.WithHomeDirectory(existingHomeDirectory) +// .WithWorkspaceCleaningSettings(cleanerDelay, deleteWorkspacesOlderThan); +// }) +// .Build(CancellationToken); +// +// await Wait.For(() => !Directory.Exists(existingWorkspaceDirectoryWithLogFile), +// TimeSpan.FromSeconds(60), +// () => throw new Exception("Workspace directory did not get deleted"), +// CancellationToken); +// Directory.Exists(existingWorkspaceDirectoryWithoutLogFile).Should().BeTrue(); +// } +// +// [Test] +// [TentacleConfigurations(testDefaultTentacleRuntimeOnly: true)] +// public async Task WhenTentacleStarts_WithWorkspaceYoungerThanThreshold_ThenWorkspaceIsLeftAlone(TentacleConfigurationTestCase tentacleConfigurationTestCase) +// { +// var cleanerDelay = TimeSpan.FromMilliseconds(500); +// var deleteWorkspacesOlderThan = TimeSpan.FromMinutes(30); +// +// var existingHomeDirectory = new TemporaryDirectory(); +// +// var existingWorkspaceDirectory = GivenExistingWorkspaceExists(existingHomeDirectory); +// await File.WriteAllTextAsync(ScriptWorkspace.GetLogFilePath(existingWorkspaceDirectory), "Existing log file"); +// +// await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() +// .WithTentacle(b => +// { +// b.WithHomeDirectory(existingHomeDirectory) +// .WithWorkspaceCleaningSettings(cleanerDelay, deleteWorkspacesOlderThan); +// }) +// .Build(CancellationToken); +// +// await Task.Delay(1000, CancellationToken); +// +// Directory.Exists(existingWorkspaceDirectory).Should().BeTrue(); +// } +// +// static string GetWorkspaceDirectoryPath(string homeDirectory, string scriptTicket) +// { +// var workspaceDirectory = Path.Combine( +// homeDirectory, +// ScriptWorkspaceFactory.WorkDirectory, +// scriptTicket); +// return workspaceDirectory; +// } +// +// static string GivenExistingWorkspaceExists(TemporaryDirectory existingHomeDirectory) +// { +// var existingWorkspaceDirectory = GetWorkspaceDirectoryPath(existingHomeDirectory.DirectoryPath, Guid.NewGuid().ToString()); +// Directory.CreateDirectory(existingWorkspaceDirectory); +// return existingWorkspaceDirectory; +// } +// +// static string GetBootstrapScriptFilePath(string workingDirectory) +// { +// return !PlatformDetection.IsRunningOnWindows +// ? BashScriptWorkspace.GetBashBootstrapScriptFilePath(workingDirectory) +// : ScriptWorkspace.GetBootstrapScriptFilePath(workingDirectory); +// } +// } +// } \ No newline at end of file