From d261933888a674d1b1342da38d52181e03c1516e Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 16 Oct 2021 18:37:31 +0200 Subject: [PATCH 01/52] Add test proxy endpoint --- .../MyService.TestProxy.csproj | 16 ++++++++++++++ src/MyService.TestProxy/Program.cs | 22 +++++++++++++++++++ src/NServiceBus.IntegrationTesting.sln | 16 +++++++++----- 3 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 src/MyService.TestProxy/MyService.TestProxy.csproj create mode 100644 src/MyService.TestProxy/Program.cs diff --git a/src/MyService.TestProxy/MyService.TestProxy.csproj b/src/MyService.TestProxy/MyService.TestProxy.csproj new file mode 100644 index 00000000..802203e3 --- /dev/null +++ b/src/MyService.TestProxy/MyService.TestProxy.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp3.1 + latest + + + + + + + + + + diff --git a/src/MyService.TestProxy/Program.cs b/src/MyService.TestProxy/Program.cs new file mode 100644 index 00000000..423d7b7e --- /dev/null +++ b/src/MyService.TestProxy/Program.cs @@ -0,0 +1,22 @@ +using System; +using Microsoft.Extensions.Hosting; +using NServiceBus; +using Serilog; + +namespace MyService.TestProxy +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + var builder = MyService.Program.CreateHostBuilder(args, configEndpointForTests => { }); + + return builder; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting.sln b/src/NServiceBus.IntegrationTesting.sln index cb936d29..2a18995e 100644 --- a/src/NServiceBus.IntegrationTesting.sln +++ b/src/NServiceBus.IntegrationTesting.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29020.237 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31808.319 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyService", "MyService\MyService.csproj", "{AB7F9FB9-B79B-47BF-A42E-CD6DB62E0E40}" EndProject @@ -15,11 +15,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.IntegrationTest EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.AssemblyScanner.Extensions", "NServiceBus.AssemblyScanner.Extensions\NServiceBus.AssemblyScanner.Extensions.csproj", "{DA921CE7-6B53-4196-8567-B0197E773043}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.IntegrationTesting.Tests", "NServiceBus.IntegrationTesting.Tests\NServiceBus.IntegrationTesting.Tests.csproj", "{18A4490B-641E-471C-9A53-02C4AAB69551}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.IntegrationTesting.Tests", "NServiceBus.IntegrationTesting.Tests\NServiceBus.IntegrationTesting.Tests.csproj", "{18A4490B-641E-471C-9A53-02C4AAB69551}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snippets", "Snippets\Snippets.csproj", "{927BC675-5940-43C6-BB97-96DA64CBA6D0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snippets", "Snippets\Snippets.csproj", "{927BC675-5940-43C6-BB97-96DA64CBA6D0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.IntegrationTesting.Tests.TestEndpoint", "NServiceBus.IntegrationTesting.Tests.TestEndpoint\NServiceBus.IntegrationTesting.Tests.TestEndpoint.csproj", "{A830D9C2-07D2-49F3-996A-9AE9EE85B38B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.IntegrationTesting.Tests.TestEndpoint", "NServiceBus.IntegrationTesting.Tests.TestEndpoint\NServiceBus.IntegrationTesting.Tests.TestEndpoint.csproj", "{A830D9C2-07D2-49F3-996A-9AE9EE85B38B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyService.TestProxy", "MyService.TestProxy\MyService.TestProxy.csproj", "{B97F3CE4-5247-4490-93F0-DD382A3209C2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -63,6 +65,10 @@ Global {A830D9C2-07D2-49F3-996A-9AE9EE85B38B}.Debug|Any CPU.Build.0 = Debug|Any CPU {A830D9C2-07D2-49F3-996A-9AE9EE85B38B}.Release|Any CPU.ActiveCfg = Release|Any CPU {A830D9C2-07D2-49F3-996A-9AE9EE85B38B}.Release|Any CPU.Build.0 = Release|Any CPU + {B97F3CE4-5247-4490-93F0-DD382A3209C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B97F3CE4-5247-4490-93F0-DD382A3209C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B97F3CE4-5247-4490-93F0-DD382A3209C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B97F3CE4-5247-4490-93F0-DD382A3209C2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From a3be73ba0bb16544c5aaec1ce22b676c3c37cf98 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 16 Oct 2021 18:39:20 +0200 Subject: [PATCH 02/52] Make so endpoints use incompatible target frameworks ad remove not needed dependencies --- src/MyOtherService/MyOtherService.csproj | 2 +- src/MyService/MyService.csproj | 4 +--- src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/MyOtherService/MyOtherService.csproj b/src/MyOtherService/MyOtherService.csproj index c2ead6bf..85c6fe08 100644 --- a/src/MyOtherService/MyOtherService.csproj +++ b/src/MyOtherService/MyOtherService.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1;net48 + net48 latest diff --git a/src/MyService/MyService.csproj b/src/MyService/MyService.csproj index 29ea5f82..632bcb3f 100644 --- a/src/MyService/MyService.csproj +++ b/src/MyService/MyService.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1;net48 + netcoreapp3.1 latest @@ -11,7 +11,6 @@ - @@ -19,7 +18,6 @@ - diff --git a/src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj b/src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj index b4bdb618..a4d32b94 100644 --- a/src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj +++ b/src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;net48 + net48 false @@ -21,7 +21,6 @@ - From 7fd2c0c59ccaac7d1818a538d0d68336b7b0eda4 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 16 Oct 2021 18:41:08 +0200 Subject: [PATCH 03/52] Stop filtering out assemblies --- src/MyService/MyServiceConfiguration.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MyService/MyServiceConfiguration.cs b/src/MyService/MyServiceConfiguration.cs index a7fc1a9a..ee8f4278 100644 --- a/src/MyService/MyServiceConfiguration.cs +++ b/src/MyService/MyServiceConfiguration.cs @@ -9,7 +9,6 @@ public MyServiceConfiguration() : base("MyService") { var scanner = this.AssemblyScanner(); - scanner.IncludeOnly("MyService.dll", "MyMessages.dll"); this.UseSerialization(); this.UsePersistence(); From e88b4b20a2057660c7e0d670b122bce467bda573 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 16 Oct 2021 18:41:37 +0200 Subject: [PATCH 04/52] Spike hypothetical test API --- src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs index f3e2fefe..4343d1c3 100644 --- a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs +++ b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs @@ -30,7 +30,7 @@ public async Task ASaga_is_completed() { var theExpectedIdentifier = Guid.NewGuid(); var context = await Scenario.Define() - .WithGenericHostEndpoint("MyService", configPreview => Program.CreateHostBuilder(new string[0], configPreview).Build(), behavior => + .WithOutOfProcessEndpoint("MyService", EndpointRunner.FromprojectReference("MyService.TestProxy"), behavior => { behavior.When(session => { From 6b4c7c778459bde7e02b10d9305b84ec63faf411 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 16 Oct 2021 19:23:03 +0200 Subject: [PATCH 05/52] Scenario context clients --- .../InterceptSendOperations.cs | 52 +++++++++++++++++++ ...ntegrationTesting.OutOfProcess.Nsb8.csproj | 15 ++++++ .../IntegrationScenarioContextClient.cs | 12 +++++ ...Bus.IntegrationTesting.OutOfProcess.csproj | 11 ++++ src/NServiceBus.IntegrationTesting.sln | 12 +++++ 5 files changed, 102 insertions(+) create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8.csproj create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/IntegrationScenarioContextClient.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs new file mode 100644 index 00000000..da54a21f --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs @@ -0,0 +1,52 @@ +using NServiceBus.Pipeline; +using System; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting.OutOfProcess.Nsb8 +{ + class InterceptSendOperations : Behavior + { + readonly string endpointName; + readonly IntegrationScenarioContextClient integrationContext; + + public InterceptSendOperations(string endpointName, IntegrationScenarioContextClient integrationContext) + { + this.endpointName = endpointName; + this.integrationContext = integrationContext; + } + + public override async Task Invoke(IOutgoingSendContext context, Func next) + { + OutgoingMessageOperation outgoingOperation; + if (context.Headers.ContainsKey(Headers.IsSagaTimeoutMessage) && context.Headers[Headers.IsSagaTimeoutMessage] == bool.TrueString) + { + outgoingOperation = new RequestTimeoutOperation() + { + SagaId = context.Headers[Headers.SagaId], + SagaTypeAssemblyQualifiedName = context.Headers[Headers.SagaType] + }; + } + else + { + outgoingOperation = new SendOperation(); + } + + outgoingOperation.SenderEndpoint = endpointName; + outgoingOperation.MessageId = context.MessageId; + outgoingOperation.MessageType = context.Message.MessageType; + outgoingOperation.MessageInstance = context.Message.Instance; + outgoingOperation.MessageHeaders = context.Headers; + + integrationContext.AddOutogingOperation(outgoingOperation); + try + { + await next(); + } + catch (Exception sendError) + { + outgoingOperation.OperationError = sendError; + throw; + } + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8.csproj b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8.csproj new file mode 100644 index 00000000..e045769c --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp3.1;net48 + + + + + + + + + + + diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/IntegrationScenarioContextClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/IntegrationScenarioContextClient.cs new file mode 100644 index 00000000..8ee7a204 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/IntegrationScenarioContextClient.cs @@ -0,0 +1,12 @@ +using System; + +namespace NServiceBus.IntegrationTesting.OutOfProcess +{ + public class IntegrationScenarioContextClient + { + public void AddOutogingOperation(OutgoingMessageOperation outgoingOperation) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj b/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj new file mode 100644 index 00000000..5fd9e971 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/src/NServiceBus.IntegrationTesting.sln b/src/NServiceBus.IntegrationTesting.sln index 2a18995e..1871d00e 100644 --- a/src/NServiceBus.IntegrationTesting.sln +++ b/src/NServiceBus.IntegrationTesting.sln @@ -23,6 +23,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.IntegrationTest EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyService.TestProxy", "MyService.TestProxy\MyService.TestProxy.csproj", "{B97F3CE4-5247-4490-93F0-DD382A3209C2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.IntegrationTesting.OutOfProcess.Nsb8", "NServiceBus.IntegrationTesting.OutOfProcess.Nsb8\NServiceBus.IntegrationTesting.OutOfProcess.Nsb8.csproj", "{E79EB2FD-6C56-4DAF-8E4B-D819D5F7EC0B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.IntegrationTesting.OutOfProcess", "NServiceBus.IntegrationTesting.OutOfProcess\NServiceBus.IntegrationTesting.OutOfProcess.csproj", "{AEF55741-1636-427C-8CD0-AF6494B8F9A7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +73,14 @@ Global {B97F3CE4-5247-4490-93F0-DD382A3209C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {B97F3CE4-5247-4490-93F0-DD382A3209C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {B97F3CE4-5247-4490-93F0-DD382A3209C2}.Release|Any CPU.Build.0 = Release|Any CPU + {E79EB2FD-6C56-4DAF-8E4B-D819D5F7EC0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E79EB2FD-6C56-4DAF-8E4B-D819D5F7EC0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E79EB2FD-6C56-4DAF-8E4B-D819D5F7EC0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E79EB2FD-6C56-4DAF-8E4B-D819D5F7EC0B}.Release|Any CPU.Build.0 = Release|Any CPU + {AEF55741-1636-427C-8CD0-AF6494B8F9A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEF55741-1636-427C-8CD0-AF6494B8F9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEF55741-1636-427C-8CD0-AF6494B8F9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEF55741-1636-427C-8CD0-AF6494B8F9A7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From aaa4abab112cbcc16c53d45d419a3598d0f37f97 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 16 Oct 2021 22:13:47 +0200 Subject: [PATCH 06/52] Begin moving types to shared contract classes --- .../MyService.TestProxy.csproj | 4 +- src/MyService.TestProxy/Program.cs | 5 +- .../When_requesting_a_timeout.cs | 85 +++++++-------- .../When_sending_AMessage.cs | 103 +++++++++--------- .../When_sending_CompleteASaga.cs | 3 +- .../Invocations.cs | 26 +++++ ...ServiceBus.IntegrationTesting.Model.csproj | 7 ++ .../OutgoingMessages.cs | 16 +-- .../EndpointConfigurationExtensions.cs | 18 +++ .../InterceptSendOperations.cs | 2 +- ...Bus.IntegrationTesting.OutOfProcess.csproj | 4 + src/NServiceBus.IntegrationTesting.sln | 6 + .../EndpointLifetime.cs | 12 ++ .../Invocations.cs | 26 ----- .../NServiceBus.IntegrationTesting.csproj | 4 + .../ScenarioWithEndpointBehaviorExtensions.cs | 10 ++ 16 files changed, 195 insertions(+), 136 deletions(-) create mode 100644 src/NServiceBus.IntegrationTesting.Model/Invocations.cs create mode 100644 src/NServiceBus.IntegrationTesting.Model/NServiceBus.IntegrationTesting.Model.csproj rename src/{NServiceBus.IntegrationTesting => NServiceBus.IntegrationTesting.Model}/OutgoingMessages.cs (52%) create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointConfigurationExtensions.cs create mode 100644 src/NServiceBus.IntegrationTesting/EndpointLifetime.cs delete mode 100644 src/NServiceBus.IntegrationTesting/Invocations.cs diff --git a/src/MyService.TestProxy/MyService.TestProxy.csproj b/src/MyService.TestProxy/MyService.TestProxy.csproj index 802203e3..4987b9f9 100644 --- a/src/MyService.TestProxy/MyService.TestProxy.csproj +++ b/src/MyService.TestProxy/MyService.TestProxy.csproj @@ -6,11 +6,9 @@ latest - - - + diff --git a/src/MyService.TestProxy/Program.cs b/src/MyService.TestProxy/Program.cs index 423d7b7e..c7ee5129 100644 --- a/src/MyService.TestProxy/Program.cs +++ b/src/MyService.TestProxy/Program.cs @@ -14,7 +14,10 @@ public static void Main(string[] args) public static IHostBuilder CreateHostBuilder(string[] args) { - var builder = MyService.Program.CreateHostBuilder(args, configEndpointForTests => { }); + var builder = MyService.Program.CreateHostBuilder(args, endpointConfiguration => + { + endpointConfiguration.EnableOutOfProcessIntegrationTesting("MyService"); + }); return builder; } diff --git a/src/MySystem.AcceptanceTests/When_requesting_a_timeout.cs b/src/MySystem.AcceptanceTests/When_requesting_a_timeout.cs index 060a9735..457d84aa 100644 --- a/src/MySystem.AcceptanceTests/When_requesting_a_timeout.cs +++ b/src/MySystem.AcceptanceTests/When_requesting_a_timeout.cs @@ -1,47 +1,46 @@ -using MyMessages.Messages; -using MyService; -using NServiceBus; -using NServiceBus.AcceptanceTesting; -using NServiceBus.DelayedDelivery; -using NServiceBus.IntegrationTesting; -using NUnit.Framework; -using System; -using System.Threading.Tasks; +//using MyMessages.Messages; +//using NServiceBus; +//using NServiceBus.AcceptanceTesting; +//using NServiceBus.DelayedDelivery; +//using NServiceBus.IntegrationTesting; +//using NUnit.Framework; +//using System; +//using System.Threading.Tasks; -namespace MySystem.AcceptanceTests -{ - public class When_requesting_a_timeout - { - //TODO: renable when a compatible combination of alphas is available - //[OneTimeSetUp] - //public async Task Setup() - //{ - // await DockerCompose.Up(); - //} +//namespace MySystem.AcceptanceTests +//{ +// public class When_requesting_a_timeout +// { +// //TODO: renable when a compatible combination of alphas is available +// //[OneTimeSetUp] +// //public async Task Setup() +// //{ +// // await DockerCompose.Up(); +// //} - //[OneTimeTearDown] - //public void Teardown() - //{ - // DockerCompose.Down(); - //} +// //[OneTimeTearDown] +// //public void Teardown() +// //{ +// // DockerCompose.Down(); +// //} - [Test] - public async Task It_should_be_rescheduled_and_handled() - { - var context = await Scenario.Define(ctx => - { - ctx.RegisterTimeoutRescheduleRule((msg, delay) => new DoNotDeliverBefore(DateTime.UtcNow.AddSeconds(5))); - }) - .WithGenericHostEndpoint("MyService", configPreview => Program.CreateHostBuilder(new string[0], configPreview).Build(), behavior => - { - behavior.When(session => session.Send("MyService", new StartASaga() {AnIdentifier = Guid.NewGuid()})); - }) - .Done(ctx => ctx.MessageWasProcessedBySaga() || ctx.HasFailedMessages()) - .Run(); +// [Test] +// public async Task It_should_be_rescheduled_and_handled() +// { +// var context = await Scenario.Define(ctx => +// { +// ctx.RegisterTimeoutRescheduleRule((msg, delay) => new DoNotDeliverBefore(DateTime.UtcNow.AddSeconds(5))); +// }) +// .WithGenericHostEndpoint("MyService", configPreview => Program.CreateHostBuilder(new string[0], configPreview).Build(), behavior => +// { +// behavior.When(session => session.Send("MyService", new StartASaga() {AnIdentifier = Guid.NewGuid()})); +// }) +// .Done(ctx => ctx.MessageWasProcessedBySaga() || ctx.HasFailedMessages()) +// .Run(); - Assert.True(context.MessageWasProcessedBySaga()); - Assert.False(context.HasFailedMessages()); - Assert.False(context.HasHandlingErrors()); - } - } -} +// Assert.True(context.MessageWasProcessedBySaga()); +// Assert.False(context.HasFailedMessages()); +// Assert.False(context.HasHandlingErrors()); +// } +// } +//} diff --git a/src/MySystem.AcceptanceTests/When_sending_AMessage.cs b/src/MySystem.AcceptanceTests/When_sending_AMessage.cs index 0ac077cf..ab1dbfab 100644 --- a/src/MySystem.AcceptanceTests/When_sending_AMessage.cs +++ b/src/MySystem.AcceptanceTests/When_sending_AMessage.cs @@ -1,58 +1,57 @@ -using MyMessages.Messages; -using MyService; -using NServiceBus; -using NServiceBus.AcceptanceTesting; -using NServiceBus.IntegrationTesting; -using NUnit.Framework; -using System; -using System.Linq; -using System.Threading.Tasks; +//using MyMessages.Messages; +//using NServiceBus; +//using NServiceBus.AcceptanceTesting; +//using NServiceBus.IntegrationTesting; +//using NUnit.Framework; +//using System; +//using System.Linq; +//using System.Threading.Tasks; -namespace MySystem.AcceptanceTests -{ - public class When_sending_AMessage - { - //TODO: renable when a compatible combination of alphas is available - //[OneTimeSetUp] - //public async Task Setup() - //{ - // await DockerCompose.Up(); - //} +//namespace MySystem.AcceptanceTests +//{ +// public class When_sending_AMessage +// { +// //TODO: renable when a compatible combination of alphas is available +// //[OneTimeSetUp] +// //public async Task Setup() +// //{ +// // await DockerCompose.Up(); +// //} - //[OneTimeTearDown] - //public void Teardown() - //{ - // DockerCompose.Down(); - //} +// //[OneTimeTearDown] +// //public void Teardown() +// //{ +// // DockerCompose.Down(); +// //} - [Test] - public async Task AReplyMessage_is_received_and_ASaga_is_started() - { - var theExpectedIdentifier = Guid.NewGuid(); - var context = await Scenario.Define() - .WithGenericHostEndpoint("MyService", configPreview => Program.CreateHostBuilder(new string[0], configPreview).Build(), behavior => - { - behavior.When(session => session.Send(new AMessage() {AnIdentifier = theExpectedIdentifier})); - }) - .WithEndpoint() - .Done(c => c.SagaWasInvoked() || c.HasFailedMessages()) - .Run(); +// [Test] +// public async Task AReplyMessage_is_received_and_ASaga_is_started() +// { +// var theExpectedIdentifier = Guid.NewGuid(); +// var context = await Scenario.Define() +// .WithGenericHostEndpoint("MyService", configPreview => Program.CreateHostBuilder(new string[0], configPreview).Build(), behavior => +// { +// behavior.When(session => session.Send(new AMessage() {AnIdentifier = theExpectedIdentifier})); +// }) +// .WithEndpoint() +// .Done(c => c.SagaWasInvoked() || c.HasFailedMessages()) +// .Run(); - var invokedSaga = context.InvokedSagas.Single(s => s.SagaType == typeof(ASaga)); +// var invokedSaga = context.InvokedSagas.Single(s => s.SagaType == typeof(ASaga)); - Assert.True(invokedSaga.IsNew); - Assert.AreEqual("MyService", invokedSaga.EndpointName); - Assert.True(((ASagaData)invokedSaga.SagaData).AnIdentifier == theExpectedIdentifier); - Assert.False(context.HasFailedMessages()); - Assert.False(context.HasHandlingErrors()); - } +// Assert.True(invokedSaga.IsNew); +// Assert.AreEqual("MyService", invokedSaga.EndpointName); +// Assert.True(((ASagaData)invokedSaga.SagaData).AnIdentifier == theExpectedIdentifier); +// Assert.False(context.HasFailedMessages()); +// Assert.False(context.HasHandlingErrors()); +// } - class MyOtherServiceEndpoint : EndpointConfigurationBuilder - { - public MyOtherServiceEndpoint() - { - EndpointSetup(); - } - } - } -} +// class MyOtherServiceEndpoint : EndpointConfigurationBuilder +// { +// public MyOtherServiceEndpoint() +// { +// EndpointSetup(); +// } +// } +// } +//} diff --git a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs index 4343d1c3..94108735 100644 --- a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs +++ b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs @@ -1,5 +1,4 @@ using MyMessages.Messages; -using MyService; using NServiceBus; using NServiceBus.AcceptanceTesting; using NServiceBus.IntegrationTesting; @@ -30,7 +29,7 @@ public async Task ASaga_is_completed() { var theExpectedIdentifier = Guid.NewGuid(); var context = await Scenario.Define() - .WithOutOfProcessEndpoint("MyService", EndpointRunner.FromprojectReference("MyService.TestProxy"), behavior => + .WithOutOfProcessEndpoint("MyService", EndpointLifetime.FromSolutionProject("MyService.TestProxy"), behavior => { behavior.When(session => { diff --git a/src/NServiceBus.IntegrationTesting.Model/Invocations.cs b/src/NServiceBus.IntegrationTesting.Model/Invocations.cs new file mode 100644 index 00000000..2946f0b0 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.Model/Invocations.cs @@ -0,0 +1,26 @@ +using System; + +namespace NServiceBus.IntegrationTesting +{ + public abstract class Invocation + { + public string EndpointName { get; set; } + public object Message { get; set; } + public Type MessageType { get; set; } + public Exception HandlingError { get; set; } + } + + public class HandlerInvocation : Invocation + { + public Type HandlerType { get; set; } + } + + public class SagaInvocation : Invocation + { + public Type SagaType { get; set; } + public bool NotFound { get; set; } + public bool IsNew { get; set; } + public bool IsCompleted { get; set; } + public object SagaData { get; set; } + } +} diff --git a/src/NServiceBus.IntegrationTesting.Model/NServiceBus.IntegrationTesting.Model.csproj b/src/NServiceBus.IntegrationTesting.Model/NServiceBus.IntegrationTesting.Model.csproj new file mode 100644 index 00000000..9f5c4f4a --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.Model/NServiceBus.IntegrationTesting.Model.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/src/NServiceBus.IntegrationTesting/OutgoingMessages.cs b/src/NServiceBus.IntegrationTesting.Model/OutgoingMessages.cs similarity index 52% rename from src/NServiceBus.IntegrationTesting/OutgoingMessages.cs rename to src/NServiceBus.IntegrationTesting.Model/OutgoingMessages.cs index 70aa5658..26f2a627 100644 --- a/src/NServiceBus.IntegrationTesting/OutgoingMessages.cs +++ b/src/NServiceBus.IntegrationTesting.Model/OutgoingMessages.cs @@ -5,12 +5,12 @@ namespace NServiceBus.IntegrationTesting { public abstract class OutgoingMessageOperation { - public string SenderEndpoint { get; internal set; } - public string MessageId { get; internal set; } - public Type MessageType { get; internal set; } - public object MessageInstance { get; internal set; } - public Dictionary MessageHeaders { get; internal set; } - public Exception OperationError { get; internal set; } + public string SenderEndpoint { get; set; } + public string MessageId { get; set; } + public Type MessageType { get; set; } + public object MessageInstance { get; set; } + public Dictionary MessageHeaders { get; set; } + public Exception OperationError { get; set; } } public class SendOperation : OutgoingMessageOperation { } @@ -21,7 +21,7 @@ public class PublishOperation : OutgoingMessageOperation { } public class RequestTimeoutOperation : SendOperation { - public string SagaId { get; internal set; } - public string SagaTypeAssemblyQualifiedName { get; internal set; } + public string SagaId { get; set; } + public string SagaTypeAssemblyQualifiedName { get; set; } } } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointConfigurationExtensions.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointConfigurationExtensions.cs new file mode 100644 index 00000000..1c5fba5a --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointConfigurationExtensions.cs @@ -0,0 +1,18 @@ +using NServiceBus.IntegrationTesting.OutOfProcess; + +namespace NServiceBus +{ + public static class EndpointConfigurationExtensions + { + public static void EnableOutOfProcessIntegrationTesting(this EndpointConfiguration builder, string endpointName) + { + var integrationScenarioContextClient = new IntegrationScenarioContextClient(); + + builder.Pipeline.Register(new InterceptSendOperations(endpointName, integrationScenarioContextClient), "Intercept send operations reporting them to the remote test engine."); + //builder.Pipeline.Register(new InterceptPublishOperations(endpointName, integrationScenarioContextClient), "Intercept publish operations reporting them to the remote test engine."); + //builder.Pipeline.Register(new InterceptReplyOperations(endpointName, integrationScenarioContextClient), "Intercept reply operations reporting them to the remote test engine."); + //builder.Pipeline.Register(new InterceptInvokedHandlers(endpointName, integrationScenarioContextClient), "Intercept invoked Message Handlers and Sagas reporting them to the remote test engine."); + //builder.Pipeline.Register(new RescheduleTimeoutsBehavior(integrationScenarioContextClient), "Intercept serialized message dispatch reporting them to the remote test engine."); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs index da54a21f..e155b98b 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs @@ -2,7 +2,7 @@ using System; using System.Threading.Tasks; -namespace NServiceBus.IntegrationTesting.OutOfProcess.Nsb8 +namespace NServiceBus.IntegrationTesting.OutOfProcess { class InterceptSendOperations : Behavior { diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj b/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj index 5fd9e971..06e299ea 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj @@ -8,4 +8,8 @@ + + + + diff --git a/src/NServiceBus.IntegrationTesting.sln b/src/NServiceBus.IntegrationTesting.sln index 1871d00e..16eda344 100644 --- a/src/NServiceBus.IntegrationTesting.sln +++ b/src/NServiceBus.IntegrationTesting.sln @@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.IntegrationTest EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.IntegrationTesting.OutOfProcess", "NServiceBus.IntegrationTesting.OutOfProcess\NServiceBus.IntegrationTesting.OutOfProcess.csproj", "{AEF55741-1636-427C-8CD0-AF6494B8F9A7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.IntegrationTesting.Model", "NServiceBus.IntegrationTesting.Model\NServiceBus.IntegrationTesting.Model.csproj", "{ABC00FEA-AE54-43F5-BCFC-85AEEA90E006}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -81,6 +83,10 @@ Global {AEF55741-1636-427C-8CD0-AF6494B8F9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU {AEF55741-1636-427C-8CD0-AF6494B8F9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU {AEF55741-1636-427C-8CD0-AF6494B8F9A7}.Release|Any CPU.Build.0 = Release|Any CPU + {ABC00FEA-AE54-43F5-BCFC-85AEEA90E006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABC00FEA-AE54-43F5-BCFC-85AEEA90E006}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABC00FEA-AE54-43F5-BCFC-85AEEA90E006}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABC00FEA-AE54-43F5-BCFC-85AEEA90E006}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/NServiceBus.IntegrationTesting/EndpointLifetime.cs b/src/NServiceBus.IntegrationTesting/EndpointLifetime.cs new file mode 100644 index 00000000..7f57198f --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/EndpointLifetime.cs @@ -0,0 +1,12 @@ +using System; + +namespace NServiceBus.IntegrationTesting +{ + public class EndpointLifetime + { + public static EndpointLifetime FromSolutionProject(string projectName) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting/Invocations.cs b/src/NServiceBus.IntegrationTesting/Invocations.cs deleted file mode 100644 index 3836198d..00000000 --- a/src/NServiceBus.IntegrationTesting/Invocations.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace NServiceBus.IntegrationTesting -{ - public abstract class Invocation - { - public string EndpointName { get; internal set; } - public object Message { get; internal set; } - public Type MessageType { get; internal set; } - public Exception HandlingError { get; internal set; } - } - - public class HandlerInvocation : Invocation - { - public Type HandlerType { get; internal set; } - } - - public class SagaInvocation : Invocation - { - public Type SagaType { get; internal set; } - public bool NotFound { get; internal set; } - public bool IsNew { get; internal set; } - public bool IsCompleted { get; internal set; } - public IContainSagaData SagaData { get; internal set; } - } -} diff --git a/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj b/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj index 721cd192..1edc7a7c 100644 --- a/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj +++ b/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj @@ -37,6 +37,10 @@ + + + + $(MinVerMajor).$(MinVerMinor).$(MinVerPatch)-pr.$(APPVEYOR_PULL_REQUEST_NUMBER).build-id.$(APPVEYOR_BUILD_ID).$(MinVerPreRelease) diff --git a/src/NServiceBus.IntegrationTesting/ScenarioWithEndpointBehaviorExtensions.cs b/src/NServiceBus.IntegrationTesting/ScenarioWithEndpointBehaviorExtensions.cs index 3c3ef31e..1e569387 100644 --- a/src/NServiceBus.IntegrationTesting/ScenarioWithEndpointBehaviorExtensions.cs +++ b/src/NServiceBus.IntegrationTesting/ScenarioWithEndpointBehaviorExtensions.cs @@ -17,5 +17,15 @@ public static IScenarioWithEndpointBehavior WithGenericHostEndpoint WithOutOfProcessEndpoint(this IScenarioWithEndpointBehavior scenarioWithEndpoint, string endpointName, EndpointLifetime lifetime, Action> behavior = null) where TContext : IntegrationScenarioContext + { + var behaviorBuilder = new OutOfProcessEndpointBehaviorBuilder(); + behavior?.Invoke(behaviorBuilder); + + scenarioWithEndpoint.WithComponent(new OutOfProcesEndpointBehavior(endpointName, lifetime, behaviorBuilder.Whens)); + + return scenarioWithEndpoint; + } } } \ No newline at end of file From 597535fd996e997b37de10b660c8b03e84fce59b Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 5 Jan 2022 16:49:50 +0100 Subject: [PATCH 07/52] external process handling --- .../MyService.TestProxy.csproj | 2 +- src/MyService.TestProxy/Program.cs | 5 + .../When_sending_CompleteASaga.cs | 39 ++--- .../EndpointLifetime.cs | 12 -- .../EndpointReference.cs | 48 ++++++ .../OutOfProcessEndpointBehavior.cs | 41 +++++ .../OutOfProcessEndpointBehaviorBuilder.cs | 51 ++++++ .../OutOfProcessEndpointRunner.cs | 145 ++++++++++++++++++ .../ScenarioWithEndpointBehaviorExtensions.cs | 4 +- 9 files changed, 313 insertions(+), 34 deletions(-) delete mode 100644 src/NServiceBus.IntegrationTesting/EndpointLifetime.cs create mode 100644 src/NServiceBus.IntegrationTesting/EndpointReference.cs create mode 100644 src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs create mode 100644 src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehaviorBuilder.cs create mode 100644 src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs diff --git a/src/MyService.TestProxy/MyService.TestProxy.csproj b/src/MyService.TestProxy/MyService.TestProxy.csproj index 4987b9f9..7a8bcb3f 100644 --- a/src/MyService.TestProxy/MyService.TestProxy.csproj +++ b/src/MyService.TestProxy/MyService.TestProxy.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + netcoreapp3.1 latest diff --git a/src/MyService.TestProxy/Program.cs b/src/MyService.TestProxy/Program.cs index c7ee5129..c48b1b8b 100644 --- a/src/MyService.TestProxy/Program.cs +++ b/src/MyService.TestProxy/Program.cs @@ -14,6 +14,11 @@ public static void Main(string[] args) public static IHostBuilder CreateHostBuilder(string[] args) { + foreach (var item in args) + { + Console.WriteLine("Argument: " + item); + } + var builder = MyService.Program.CreateHostBuilder(args, endpointConfiguration => { endpointConfiguration.EnableOutOfProcessIntegrationTesting("MyService"); diff --git a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs index 94108735..2e3cbc37 100644 --- a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs +++ b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs @@ -29,33 +29,34 @@ public async Task ASaga_is_completed() { var theExpectedIdentifier = Guid.NewGuid(); var context = await Scenario.Define() - .WithOutOfProcessEndpoint("MyService", EndpointLifetime.FromSolutionProject("MyService.TestProxy"), behavior => + .WithOutOfProcessEndpoint("MyService", EndpointReference.FromSolutionProject("MyService.TestProxy"), behavior => { - behavior.When(session => - { - return session.Send("MyService", new StartASaga() {AnIdentifier = theExpectedIdentifier}); - }); - behavior.When(condition: ctx => - { - return ctx.SagaWasInvoked() && ctx.InvokedSagas.Any(s=> s.SagaType == typeof(ASaga) && s.IsNew); - }, - action: session => - { - return session.Send("MyService", new CompleteASaga {AnIdentifier = theExpectedIdentifier}); - }); + //behavior.When(session => + //{ + // return session.Send("MyService", new StartASaga() {AnIdentifier = theExpectedIdentifier}); + //}); + //behavior.When(condition: ctx => + //{ + // return ctx.SagaWasInvoked() && ctx.InvokedSagas.Any(s=> s.SagaType == typeof(ASaga) && s.IsNew); + //}, + //action: session => + //{ + // return session.Send("MyService", new CompleteASaga {AnIdentifier = theExpectedIdentifier}); + //}); }) .Done(ctx => { - return ctx.HasFailedMessages() || ctx.InvokedSagas.Any(s => s.SagaType == typeof(ASaga) && s.IsCompleted); + return true; + //return ctx.HasFailedMessages() || ctx.InvokedSagas.Any(s => s.SagaType == typeof(ASaga) && s.IsCompleted); }) .Run(); - var invokedSagas = context.InvokedSagas.Where(s => s.SagaType == typeof(ASaga)); - var newSaga = invokedSagas.SingleOrDefault(s => s.IsNew); - var completedSaga = invokedSagas.SingleOrDefault(s => s.IsCompleted); + //var invokedSagas = context.InvokedSagas.Where(s => s.SagaType == typeof(ASaga)); + //var newSaga = invokedSagas.SingleOrDefault(s => s.IsNew); + //var completedSaga = invokedSagas.SingleOrDefault(s => s.IsCompleted); - Assert.IsNotNull(newSaga); - Assert.IsNotNull(completedSaga); + //Assert.IsNotNull(newSaga); + //Assert.IsNotNull(completedSaga); Assert.False(context.HasFailedMessages()); Assert.False(context.HasHandlingErrors()); } diff --git a/src/NServiceBus.IntegrationTesting/EndpointLifetime.cs b/src/NServiceBus.IntegrationTesting/EndpointLifetime.cs deleted file mode 100644 index 7f57198f..00000000 --- a/src/NServiceBus.IntegrationTesting/EndpointLifetime.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace NServiceBus.IntegrationTesting -{ - public class EndpointLifetime - { - public static EndpointLifetime FromSolutionProject(string projectName) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting/EndpointReference.cs b/src/NServiceBus.IntegrationTesting/EndpointReference.cs new file mode 100644 index 00000000..4f68c4ed --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/EndpointReference.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using System.Linq; + +namespace NServiceBus.IntegrationTesting +{ + public class EndpointReference + { + private string projectName; + + private EndpointReference(string projectName) + { + this.projectName = projectName; + } + + public static EndpointReference FromSolutionProject(string projectName) + { + return new EndpointReference(projectName); + } + + internal string GetProjectFilePath() + { + var directory = AppDomain.CurrentDomain.BaseDirectory; + + while (true) + { + if (Directory.EnumerateFiles(directory).Any(file => file.EndsWith(".sln"))) + { + var projectFilePath = Directory.EnumerateFiles(directory, $"{projectName}.csproj", SearchOption.AllDirectories).SingleOrDefault(); + if (!File.Exists(projectFilePath)) + { + throw new Exception($"Unable to find a project file that matches the supplied project name {projectName}"); + } + + return projectFilePath; + } + + var parent = Directory.GetParent(directory); + if (parent == null) + { + throw new Exception($"Unable to determine the solution directory path due to the absence of a solution file."); + } + + directory = parent.FullName; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs new file mode 100644 index 00000000..304f8c3a --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NServiceBus.AcceptanceTesting.Support; + +namespace NServiceBus.IntegrationTesting +{ + class OutOfProcessEndpointBehavior : IComponentBehavior + { + readonly EndpointReference reference; + readonly IList whens; + readonly string endpointName; + + public OutOfProcessEndpointBehavior(string endpointName, EndpointReference reference, IList whens) + { + this.endpointName = endpointName; + this.reference = reference; + this.whens = whens; + } + + public Task CreateRunner(RunDescriptor runDescriptor) + { + var process = new Process(); + process.StartInfo.FileName = @"dotnet"; + process.StartInfo.Arguments = $"run --project \"{reference.GetProjectFilePath()}\" \"--integrationTest {endpointName}\""; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + process.StartInfo.RedirectStandardInput = true; + process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + process.StartInfo.CreateNoWindow = true; + + var runner = new OutOfProcessEndpointRunner(runDescriptor, endpointName, process, whens); + return Task.FromResult((ComponentRunner)runner); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehaviorBuilder.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehaviorBuilder.cs new file mode 100644 index 00000000..56c35e8a --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehaviorBuilder.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using NServiceBus.AcceptanceTesting; +using NServiceBus.AcceptanceTesting.Support; + +namespace NServiceBus.IntegrationTesting +{ + public class OutOfProcessEndpointBehaviorBuilder where TContext : ScenarioContext + { + public OutOfProcessEndpointBehaviorBuilder When(Func action) + { + return When(c => true, action); + } + + public OutOfProcessEndpointBehaviorBuilder When(Func action) + { + return When(c => true, action); + } + + public OutOfProcessEndpointBehaviorBuilder When(Func> condition, Func action) + { + Whens.Add(new WhenDefinition(condition, action)); + + return this; + } + + public OutOfProcessEndpointBehaviorBuilder When(Predicate condition, Func action) + { + Whens.Add(new WhenDefinition(ctx => Task.FromResult(condition(ctx)), action)); + + return this; + } + + public OutOfProcessEndpointBehaviorBuilder When(Func> condition, Func action) + { + Whens.Add(new WhenDefinition(condition, action)); + + return this; + } + + public OutOfProcessEndpointBehaviorBuilder When(Predicate condition, Func action) + { + Whens.Add(new WhenDefinition(ctx => Task.FromResult(condition(ctx)), action)); + + return this; + } + + public IList Whens { get; } = new List(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs new file mode 100644 index 00000000..b0e97080 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NServiceBus.AcceptanceTesting.Support; +using NServiceBus.Logging; + +namespace NServiceBus.IntegrationTesting +{ + class OutOfProcessEndpointRunner : ComponentRunner + { + static ILog Logger = LogManager.GetLogger(); + private Process process; + private Task outputTask; + private Task errorTask; + readonly RunDescriptor runDescriptor; + private readonly EndpointReference reference; + readonly IList whens; + + public OutOfProcessEndpointRunner(RunDescriptor runDescriptor, string endpointName, Process process, IList whens) + { + Name = endpointName; + this.runDescriptor = runDescriptor; + this.process = process; + this.whens = whens; + } + + public override string Name { get; } + + public override async Task Start(CancellationToken token) + { + var started = process.Start(); + + outputTask = process.StandardOutput.ReadToEndAsync(); + errorTask = process.StandardError.ReadToEndAsync(); + + EnsureEndpointIsConfiguredForTests(); + + //build the remote session proxy + IMessageSession messageSession = null; + + //TODO: How to access ScenarioContext.CurrentEndpoint + // ScenarioContext.CurrentEndpoint = Name; + try + { + if (whens.Count != 0) + { + await Task.Run(async () => + { + var executedWhens = new HashSet(); + + while (!token.IsCancellationRequested) + { + if (executedWhens.Count == whens.Count) + { + break; + } + + if (token.IsCancellationRequested) + { + break; + } + + foreach (var when in whens) + { + if (token.IsCancellationRequested) + { + break; + } + + if (executedWhens.Contains(when.Id)) + { + continue; + } + + if (await when.ExecuteAction(runDescriptor.ScenarioContext, messageSession).ConfigureAwait(false)) + { + executedWhens.Add(when.Id); + } + } + + await Task.Yield(); // enforce yield current context, tight loop could introduce starvation + } + }, token).ConfigureAwait(false); + } + } + catch (Exception ex) + { + Logger.Error($"Failed to execute Whens on endpoint{Name}", ex); + + throw; + } + } + + private void EnsureEndpointIsConfiguredForTests() + { + //TODO: ensure can communicate with remote process + } + + public override async Task Stop() + { + //TODO: How to access ScenarioContext.CurrentEndpoint + // ScenarioContext.CurrentEndpoint = Name; + try + { + //TODO: replace this with a signal sent to the remote IntegrationScenarioContext that will do Environment.Exit(); + process.Kill(); + + var output = await outputTask; + var error = await errorTask; + + if (output != string.Empty) + { + Console.WriteLine(output); + } + + if (error != string.Empty) + { + throw new Exception(error); + } + + process.Dispose(); + process = null; + } + catch (Exception ex) + { + Logger.Error("Failed to stop endpoint " + Name, ex); + throw; + } + + ThrowOnFailedMessages(); + } + + void ThrowOnFailedMessages() + { + foreach (var failedMessage in runDescriptor.ScenarioContext.FailedMessages.Where(kvp => kvp.Key == Name)) + { + throw new MessageFailedException(failedMessage.Value.First(), runDescriptor.ScenarioContext); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting/ScenarioWithEndpointBehaviorExtensions.cs b/src/NServiceBus.IntegrationTesting/ScenarioWithEndpointBehaviorExtensions.cs index 1e569387..051edc20 100644 --- a/src/NServiceBus.IntegrationTesting/ScenarioWithEndpointBehaviorExtensions.cs +++ b/src/NServiceBus.IntegrationTesting/ScenarioWithEndpointBehaviorExtensions.cs @@ -18,12 +18,12 @@ public static IScenarioWithEndpointBehavior WithGenericHostEndpoint WithOutOfProcessEndpoint(this IScenarioWithEndpointBehavior scenarioWithEndpoint, string endpointName, EndpointLifetime lifetime, Action> behavior = null) where TContext : IntegrationScenarioContext + public static IScenarioWithEndpointBehavior WithOutOfProcessEndpoint(this IScenarioWithEndpointBehavior scenarioWithEndpoint, string endpointName, EndpointReference endpointReference, Action> behavior = null) where TContext : IntegrationScenarioContext { var behaviorBuilder = new OutOfProcessEndpointBehaviorBuilder(); behavior?.Invoke(behaviorBuilder); - scenarioWithEndpoint.WithComponent(new OutOfProcesEndpointBehavior(endpointName, lifetime, behaviorBuilder.Whens)); + scenarioWithEndpoint.WithComponent(new OutOfProcessEndpointBehavior(endpointName, endpointReference, behaviorBuilder.Whens)); return scenarioWithEndpoint; } From 88d4caa5b7bc723d40bd81026494cd30063759a9 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 5 Jan 2022 18:26:26 +0100 Subject: [PATCH 08/52] Update NUnit test adapter --- src/MyService.TestProxy/MyService.TestProxy.csproj | 6 ++++-- src/MyService.TestProxy/Program.cs | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/MyService.TestProxy/MyService.TestProxy.csproj b/src/MyService.TestProxy/MyService.TestProxy.csproj index 7a8bcb3f..802203e3 100644 --- a/src/MyService.TestProxy/MyService.TestProxy.csproj +++ b/src/MyService.TestProxy/MyService.TestProxy.csproj @@ -2,13 +2,15 @@ Exe - netcoreapp3.1 + netcoreapp3.1 latest + + + - diff --git a/src/MyService.TestProxy/Program.cs b/src/MyService.TestProxy/Program.cs index c48b1b8b..68627c3b 100644 --- a/src/MyService.TestProxy/Program.cs +++ b/src/MyService.TestProxy/Program.cs @@ -21,6 +21,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) var builder = MyService.Program.CreateHostBuilder(args, endpointConfiguration => { + //TODO: get the endpoint name from command line args endpointConfiguration.EnableOutOfProcessIntegrationTesting("MyService"); }); From 6cc5eb7e774af3436f935620f25569d61772226f Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 5 Jan 2022 18:28:44 +0100 Subject: [PATCH 09/52] enhance arguments --- .../OutOfProcessEndpointBehavior.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs index 304f8c3a..bee43cbf 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs @@ -26,7 +26,7 @@ public Task CreateRunner(RunDescriptor runDescriptor) { var process = new Process(); process.StartInfo.FileName = @"dotnet"; - process.StartInfo.Arguments = $"run --project \"{reference.GetProjectFilePath()}\" \"--integrationTest {endpointName}\""; + process.StartInfo.Arguments = $"run --project \"{reference.GetProjectFilePath()}\" --integrationTest --endpointName={endpointName}"; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; From e97bb7f1cd056fa442c79725f4bd1ed741f73c51 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 5 Jan 2022 18:30:32 +0100 Subject: [PATCH 10/52] Use gRpc to control remote endpoints --- .../EndpointConfigurationExtensions.cs | 5 +++- ...Bus.IntegrationTesting.OutOfProcess.csproj | 10 +++++-- .../RemoteEndpointClient.cs | 26 +++++++++++++++++++ .../RemoteEndpointImpl.cs | 18 +++++++++++++ .../RemoteEndpointServer.cs | 18 +++++++++++++ src/NServiceBus.IntegrationTesting.sln | 6 ++--- .../NServiceBus.IntegrationTesting.csproj | 1 + .../OutOfProcessEndpointRunner.cs | 6 +++-- src/protos/RemoteEndpoint.proto | 11 ++++++++ 9 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs create mode 100644 src/protos/RemoteEndpoint.proto diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointConfigurationExtensions.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointConfigurationExtensions.cs index 1c5fba5a..fed75ecc 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointConfigurationExtensions.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointConfigurationExtensions.cs @@ -1,4 +1,5 @@ -using NServiceBus.IntegrationTesting.OutOfProcess; +using Grpc.Core; +using NServiceBus.IntegrationTesting.OutOfProcess; namespace NServiceBus { @@ -6,6 +7,8 @@ public static class EndpointConfigurationExtensions { public static void EnableOutOfProcessIntegrationTesting(this EndpointConfiguration builder, string endpointName) { + _ = new RemoteEndpointServer(); + var integrationScenarioContextClient = new IntegrationScenarioContextClient(); builder.Pipeline.Register(new InterceptSendOperations(endpointName, integrationScenarioContextClient), "Intercept send operations reporting them to the remote test engine."); diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj b/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj index 06e299ea..ba56ca43 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj @@ -1,15 +1,21 @@ - + netstandard2.0 - + + + + + + + diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs new file mode 100644 index 00000000..da800d2c --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs @@ -0,0 +1,26 @@ +using Grpc.Core; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting.OutOfProcess +{ + public class RemoteEndpointClient + { + private Channel channel; + private RemoteEndpoint.RemoteEndpointClient client; + + public RemoteEndpointClient() + { + //TODO: replace with configurable port + channel = new Channel("127.0.0.1:30051", ChannelCredentials.Insecure); + client = new RemoteEndpoint.RemoteEndpointClient(channel); + } + + public async Task Stop() + { + //cannot await: stopping the remote process kills the server + //that kills the stream tat blows up the client if awaits + _ = client.StopAsync(new Google.Protobuf.WellKnownTypes.Empty()); + await channel.ShutdownAsync(); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs new file mode 100644 index 00000000..79d07a4a --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs @@ -0,0 +1,18 @@ +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using System; +using System.Threading.Tasks; +using static NServiceBus.IntegrationTesting.OutOfProcess.RemoteEndpoint; + +namespace NServiceBus.IntegrationTesting.OutOfProcess +{ + class RemoteEndpointImpl : RemoteEndpointBase + { + public override Task Stop(Empty request, ServerCallContext context) + { + Environment.Exit(0); + + return Task.FromResult(new Empty()); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs new file mode 100644 index 00000000..2cd33141 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs @@ -0,0 +1,18 @@ +using Grpc.Core; + +namespace NServiceBus.IntegrationTesting.OutOfProcess +{ + public class RemoteEndpointServer + { + public RemoteEndpointServer() + { + Server server = new Server + { + Services = { RemoteEndpoint.BindService(new RemoteEndpointImpl()) }, + //TODO: replace with configurable port + Ports = { new ServerPort("localhost", 30051, ServerCredentials.Insecure) } + }; + server.Start(); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.sln b/src/NServiceBus.IntegrationTesting.sln index 16eda344..b95bed01 100644 --- a/src/NServiceBus.IntegrationTesting.sln +++ b/src/NServiceBus.IntegrationTesting.sln @@ -23,11 +23,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.IntegrationTest EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyService.TestProxy", "MyService.TestProxy\MyService.TestProxy.csproj", "{B97F3CE4-5247-4490-93F0-DD382A3209C2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.IntegrationTesting.OutOfProcess.Nsb8", "NServiceBus.IntegrationTesting.OutOfProcess.Nsb8\NServiceBus.IntegrationTesting.OutOfProcess.Nsb8.csproj", "{E79EB2FD-6C56-4DAF-8E4B-D819D5F7EC0B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.IntegrationTesting.OutOfProcess.Nsb8", "NServiceBus.IntegrationTesting.OutOfProcess.Nsb8\NServiceBus.IntegrationTesting.OutOfProcess.Nsb8.csproj", "{E79EB2FD-6C56-4DAF-8E4B-D819D5F7EC0B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.IntegrationTesting.OutOfProcess", "NServiceBus.IntegrationTesting.OutOfProcess\NServiceBus.IntegrationTesting.OutOfProcess.csproj", "{AEF55741-1636-427C-8CD0-AF6494B8F9A7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.IntegrationTesting.OutOfProcess", "NServiceBus.IntegrationTesting.OutOfProcess\NServiceBus.IntegrationTesting.OutOfProcess.csproj", "{AEF55741-1636-427C-8CD0-AF6494B8F9A7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.IntegrationTesting.Model", "NServiceBus.IntegrationTesting.Model\NServiceBus.IntegrationTesting.Model.csproj", "{ABC00FEA-AE54-43F5-BCFC-85AEEA90E006}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.IntegrationTesting.Model", "NServiceBus.IntegrationTesting.Model\NServiceBus.IntegrationTesting.Model.csproj", "{ABC00FEA-AE54-43F5-BCFC-85AEEA90E006}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj b/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj index 1edc7a7c..fa01d19f 100644 --- a/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj +++ b/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj @@ -39,6 +39,7 @@ + diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs index b0e97080..6997cfdb 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs @@ -6,12 +6,14 @@ using System.Threading; using System.Threading.Tasks; using NServiceBus.AcceptanceTesting.Support; +using NServiceBus.IntegrationTesting.OutOfProcess; using NServiceBus.Logging; namespace NServiceBus.IntegrationTesting { class OutOfProcessEndpointRunner : ComponentRunner { + RemoteEndpointClient remoteEndpoint; static ILog Logger = LogManager.GetLogger(); private Process process; private Task outputTask; @@ -22,6 +24,7 @@ class OutOfProcessEndpointRunner : ComponentRunner public OutOfProcessEndpointRunner(RunDescriptor runDescriptor, string endpointName, Process process, IList whens) { + remoteEndpoint = new RemoteEndpointClient(); Name = endpointName; this.runDescriptor = runDescriptor; this.process = process; @@ -106,8 +109,7 @@ public override async Task Stop() // ScenarioContext.CurrentEndpoint = Name; try { - //TODO: replace this with a signal sent to the remote IntegrationScenarioContext that will do Environment.Exit(); - process.Kill(); + await remoteEndpoint.Stop(); var output = await outputTask; var error = await errorTask; diff --git a/src/protos/RemoteEndpoint.proto b/src/protos/RemoteEndpoint.proto new file mode 100644 index 00000000..515563f0 --- /dev/null +++ b/src/protos/RemoteEndpoint.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "NServiceBus.IntegrationTesting.OutOfProcess"; +package NServiceBus.IntegrationTesting; + +service RemoteEndpoint { + rpc Stop (google.protobuf.Empty) returns (google.protobuf.Empty); +} \ No newline at end of file From ec68a71670206569a4e097cfcbb614174aa1edda Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 5 Jan 2022 18:36:24 +0100 Subject: [PATCH 11/52] Add missing reference --- src/MyService.TestProxy/MyService.TestProxy.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/MyService.TestProxy/MyService.TestProxy.csproj b/src/MyService.TestProxy/MyService.TestProxy.csproj index 802203e3..4987b9f9 100644 --- a/src/MyService.TestProxy/MyService.TestProxy.csproj +++ b/src/MyService.TestProxy/MyService.TestProxy.csproj @@ -6,11 +6,9 @@ latest - - - + From 9f4807113d1cd61edc005fb316b3de401bf8a6bb Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 10:51:01 +0100 Subject: [PATCH 12/52] Remove the need for configuration preview callback --- src/MyService/Program.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/MyService/Program.cs b/src/MyService/Program.cs index 8f07c7a6..09ff7a5d 100644 --- a/src/MyService/Program.cs +++ b/src/MyService/Program.cs @@ -12,7 +12,7 @@ public static void Main(string[] args) CreateHostBuilder(args).Build().Run(); } - public static IHostBuilder CreateHostBuilder(string[] args, Action configPreview = null) + public static IHostBuilder CreateHostBuilder(string[] args) { var builder = Host.CreateDefaultBuilder(args); builder.UseConsoleLifetime(); @@ -25,8 +25,6 @@ public static IHostBuilder CreateHostBuilder(string[] args, Action { var config = new MyServiceConfiguration(); - configPreview?.Invoke(config); - return config; }); From fc69253d2b9512551fcd2ee52c2f92d3292d1f95 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 10:51:32 +0100 Subject: [PATCH 13/52] Make sure the tested endpoint targets only one framework --- src/MyService.TestProxy/MyService.TestProxy.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MyService.TestProxy/MyService.TestProxy.csproj b/src/MyService.TestProxy/MyService.TestProxy.csproj index 4987b9f9..7a8bcb3f 100644 --- a/src/MyService.TestProxy/MyService.TestProxy.csproj +++ b/src/MyService.TestProxy/MyService.TestProxy.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + netcoreapp3.1 latest From 2499e706197e3cb0bde130d3449d2de21f66e1f8 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 10:51:59 +0100 Subject: [PATCH 14/52] Shortcut the test proxy startup code --- src/MyService.TestProxy/Program.cs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/MyService.TestProxy/Program.cs b/src/MyService.TestProxy/Program.cs index 68627c3b..32759ff0 100644 --- a/src/MyService.TestProxy/Program.cs +++ b/src/MyService.TestProxy/Program.cs @@ -9,23 +9,7 @@ public class Program { public static void Main(string[] args) { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) - { - foreach (var item in args) - { - Console.WriteLine("Argument: " + item); - } - - var builder = MyService.Program.CreateHostBuilder(args, endpointConfiguration => - { - //TODO: get the endpoint name from command line args - endpointConfiguration.EnableOutOfProcessIntegrationTesting("MyService"); - }); - - return builder; + MyService.Program.CreateHostBuilder(args).Build().Run(); } } } \ No newline at end of file From 9022416f598c155995c0a11002a64f419193bd78 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 11:01:00 +0100 Subject: [PATCH 15/52] Simplify dealing with command line arguments --- .../CommandLine.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/CommandLine.cs diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/CommandLine.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/CommandLine.cs new file mode 100644 index 00000000..89db029d --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/CommandLine.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NServiceBus.IntegrationTesting.OutOfProcess +{ + public static class CommandLine + { + public static void EnsureIsIntegrationTest() + { + var integrationTest = Environment.CommandLine + .Split(' ') + .Where(x => x == "--integrationTest") + .SingleOrDefault(); + + if (string.IsNullOrWhiteSpace(integrationTest)) + { + throw new ArgumentException("This library is designed for integration tests usage. If this is not an integration test remove the library dependency."); + } + } + + public static string GetEndpointName() + { + var endpointName = Environment.CommandLine + .Split(' ') + .Where(x => x.StartsWith("--endpointName=")) + .Single() + .Split('=') + .Last(); + + return endpointName; + } + } +} From c2ed300371134842ba5960b41f33c2dfb30f4a7b Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 11:01:36 +0100 Subject: [PATCH 16/52] Add support for remote endpoint started notification --- ...tionExtensions.cs => AutoConfiguration.cs} | 19 +++++--- .../EndpointStartupCallback.cs | 43 +++++++++++++++++++ .../RemoteEndpointServerV8.cs | 22 ++++++++++ .../RemoteEndpointClient.cs | 11 +++++ .../RemoteEndpointImpl.cs | 26 +++++++++++ .../RemoteEndpointServer.cs | 22 ++++++++-- .../OutOfProcessEndpointRunner.cs | 19 ++++++-- src/protos/RemoteEndpoint.proto | 6 +++ 8 files changed, 154 insertions(+), 14 deletions(-) rename src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/{EndpointConfigurationExtensions.cs => AutoConfiguration.cs} (65%) create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointConfigurationExtensions.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs similarity index 65% rename from src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointConfigurationExtensions.cs rename to src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs index fed75ecc..47825e1a 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointConfigurationExtensions.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs @@ -1,13 +1,20 @@ -using Grpc.Core; -using NServiceBus.IntegrationTesting.OutOfProcess; +using Microsoft.Extensions.DependencyInjection; -namespace NServiceBus +namespace NServiceBus.IntegrationTesting.OutOfProcess.Nsb8 { - public static class EndpointConfigurationExtensions + class AutoConfiguration : INeedInitialization { - public static void EnableOutOfProcessIntegrationTesting(this EndpointConfiguration builder, string endpointName) + public void Customize(EndpointConfiguration builder) { - _ = new RemoteEndpointServer(); + CommandLine.EnsureIsIntegrationTest(); + + var endpointName = CommandLine.GetEndpointName(); + + var remoteEndpointServer = new RemoteEndpointServerV8(endpointName); + + builder.RegisterComponents(services => services.AddSingleton(remoteEndpointServer)); + + builder.EnableFeature(); var integrationScenarioContextClient = new IntegrationScenarioContextClient(); diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs new file mode 100644 index 00000000..306c28c3 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.DependencyInjection; +using NServiceBus.Features; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting.OutOfProcess.Nsb8 +{ + class EndpointStartupCallback : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask( container => + { + var server = container.GetService(); + return new CallbackStartupTask(server); + }); + } + } + + class CallbackStartupTask : FeatureStartupTask + { + readonly RemoteEndpointServerV8 _server; + + public CallbackStartupTask(RemoteEndpointServerV8 server) + { + _server = server; + } + + protected override Task OnStart(IMessageSession session, CancellationToken cancellationToken = default) + { + _server.RegisterMessageSession(session); + _server.NotifyEndpointStarted(); + + return Task.CompletedTask; + } + + protected override Task OnStop(IMessageSession session, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs new file mode 100644 index 00000000..267a1e0d --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NServiceBus.IntegrationTesting.OutOfProcess.Nsb8 +{ + internal class RemoteEndpointServerV8 : RemoteEndpointServer + { + private IMessageSession _messageSession; + + public RemoteEndpointServerV8(string endpointName) + : base(endpointName) + { + + } + + public void RegisterMessageSession(IMessageSession messageSession) + { + _messageSession = messageSession; + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs index da800d2c..d3393185 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs @@ -1,4 +1,5 @@ using Grpc.Core; +using System; using System.Threading.Tasks; namespace NServiceBus.IntegrationTesting.OutOfProcess @@ -22,5 +23,15 @@ public async Task Stop() _ = client.StopAsync(new Google.Protobuf.WellKnownTypes.Empty()); await channel.ShutdownAsync(); } + + public async Task OnEndpointStarted(Func onStarted) + { + var response = client.EndpointStarted(new Google.Protobuf.WellKnownTypes.Empty()); + while (await response.ResponseStream.MoveNext()) + { + var current = response.ResponseStream.Current; + await onStarted(current); + } + } } } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs index 79d07a4a..5983888b 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs @@ -8,11 +8,37 @@ namespace NServiceBus.IntegrationTesting.OutOfProcess { class RemoteEndpointImpl : RemoteEndpointBase { + bool endpoitStarted; + readonly string endpointName; + + public RemoteEndpointImpl(string endpointName) + { + this.endpointName = endpointName; + } + public override Task Stop(Empty request, ServerCallContext context) { Environment.Exit(0); return Task.FromResult(new Empty()); } + + public override async Task EndpointStarted(Empty request, IServerStreamWriter responseStream, ServerCallContext context) + { + while (!endpoitStarted) + { + await Task.Delay(500); + } + + await responseStream.WriteAsync(new EndpointStartedEvent() + { + EndpointName = endpointName + }); + } + + internal void NotifyEndpointStarted() + { + endpoitStarted = true; + } } } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs index 2cd33141..d213bc7d 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs @@ -1,18 +1,32 @@ using Grpc.Core; +using System.Threading.Tasks; namespace NServiceBus.IntegrationTesting.OutOfProcess { - public class RemoteEndpointServer + public abstract class RemoteEndpointServer { - public RemoteEndpointServer() + private readonly RemoteEndpointImpl remoteEndpoint; + readonly Server server; + + protected RemoteEndpointServer(string endpointName) { - Server server = new Server + remoteEndpoint = new RemoteEndpointImpl(endpointName); + EndpointName = endpointName; + + server = new Server { - Services = { RemoteEndpoint.BindService(new RemoteEndpointImpl()) }, + Services = { RemoteEndpoint.BindService(remoteEndpoint) }, //TODO: replace with configurable port Ports = { new ServerPort("localhost", 30051, ServerCredentials.Insecure) } }; server.Start(); } + + public string EndpointName { get; } + + public void NotifyEndpointStarted() + { + remoteEndpoint.NotifyEndpointStarted(); + } } } diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs index 6997cfdb..a33c42d2 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs @@ -19,7 +19,7 @@ class OutOfProcessEndpointRunner : ComponentRunner private Task outputTask; private Task errorTask; readonly RunDescriptor runDescriptor; - private readonly EndpointReference reference; + bool remoteEndpointStarted; readonly IList whens; public OutOfProcessEndpointRunner(RunDescriptor runDescriptor, string endpointName, Process process, IList whens) @@ -40,7 +40,7 @@ public override async Task Start(CancellationToken token) outputTask = process.StandardOutput.ReadToEndAsync(); errorTask = process.StandardError.ReadToEndAsync(); - EnsureEndpointIsConfiguredForTests(); + await ConnectToRemoteEndpoint(); //build the remote session proxy IMessageSession messageSession = null; @@ -98,9 +98,15 @@ await Task.Run(async () => } } - private void EnsureEndpointIsConfiguredForTests() + private Task ConnectToRemoteEndpoint() { - //TODO: ensure can communicate with remote process + return remoteEndpoint.OnEndpointStarted(e => + { + Logger.Info($"Remote endpoint '{e.EndpointName}' started."); + + remoteEndpointStarted = true; + return Task.CompletedTask; + }); } public override async Task Stop() @@ -109,6 +115,11 @@ public override async Task Stop() // ScenarioContext.CurrentEndpoint = Name; try { + while (!remoteEndpointStarted) + { + await Task.Delay(500); + } + await remoteEndpoint.Stop(); var output = await outputTask; diff --git a/src/protos/RemoteEndpoint.proto b/src/protos/RemoteEndpoint.proto index 515563f0..78be59af 100644 --- a/src/protos/RemoteEndpoint.proto +++ b/src/protos/RemoteEndpoint.proto @@ -2,10 +2,16 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; +import "google/protobuf/any.proto"; option csharp_namespace = "NServiceBus.IntegrationTesting.OutOfProcess"; package NServiceBus.IntegrationTesting; service RemoteEndpoint { rpc Stop (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc EndpointStarted (google.protobuf.Empty) returns (stream EndpointStartedEvent); +} + +message EndpointStartedEvent { + string endpointName = 1; } \ No newline at end of file From 0e4c11f84237808d44dc4fba46f40a700bc3b8c0 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 14:39:12 +0100 Subject: [PATCH 17/52] update RabbitMQ image --- src/MySystem.AcceptanceTests/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MySystem.AcceptanceTests/docker-compose.yml b/src/MySystem.AcceptanceTests/docker-compose.yml index cad9e1d3..68e46b68 100644 --- a/src/MySystem.AcceptanceTests/docker-compose.yml +++ b/src/MySystem.AcceptanceTests/docker-compose.yml @@ -3,7 +3,7 @@ services: rabbit: container_name: mysystem_acceptancetests_rabbit hostname: rabbit - image: rabbitmq:3.6.6-management + image: rabbitmq:3.8-management environment: - RABBITMQ_DEFAULT_USER=guest - RABBITMQ_DEFAULT_PASS=guest From c55969f5f38662562b01bfd52ad39c212d03f1cd Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 14:39:56 +0100 Subject: [PATCH 18/52] Update dependencies to re-enable testing against RabbitMQ --- src/MyOtherService/MyOtherService.csproj | 4 ++-- .../MyOtherServiceConfigurationBuilder.cs | 6 ++---- src/MyService/MyService.csproj | 3 ++- src/MyService/MyServiceConfiguration.cs | 7 ++----- .../MySystem.AcceptanceTests.csproj | 6 +++--- .../When_sending_CompleteASaga.cs | 21 +++++++++---------- ...rviceBus.AssemblyScanner.Extensions.csproj | 2 +- ...ntegrationTesting.OutOfProcess.Nsb8.csproj | 2 +- ...tegrationTesting.Tests.TestEndpoint.csproj | 2 +- .../NServiceBus.IntegrationTesting.csproj | 4 ++-- 10 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/MyOtherService/MyOtherService.csproj b/src/MyOtherService/MyOtherService.csproj index 85c6fe08..4dce04a1 100644 --- a/src/MyOtherService/MyOtherService.csproj +++ b/src/MyOtherService/MyOtherService.csproj @@ -7,9 +7,9 @@ - - + + diff --git a/src/MyOtherService/MyOtherServiceConfigurationBuilder.cs b/src/MyOtherService/MyOtherServiceConfigurationBuilder.cs index 97be8aa9..0500cd4f 100644 --- a/src/MyOtherService/MyOtherServiceConfigurationBuilder.cs +++ b/src/MyOtherService/MyOtherServiceConfigurationBuilder.cs @@ -14,10 +14,8 @@ public static EndpointConfiguration Build(string endpointName, string rabbitMqCo config.UsePersistence(); config.EnableInstallers(); - //TODO: renable when a compatible combination of alphas is available - //var transport = new RabbitMQTransport(Topology.Conventional, rabbitMqConnectionString); - //config.UseTransport(transport); - config.UseTransport(new LearningTransport()); + var transport = new RabbitMQTransport(Topology.Conventional, rabbitMqConnectionString); + config.UseTransport(transport); config.SendFailedMessagesTo("error"); diff --git a/src/MyService/MyService.csproj b/src/MyService/MyService.csproj index 632bcb3f..6edf6385 100644 --- a/src/MyService/MyService.csproj +++ b/src/MyService/MyService.csproj @@ -7,7 +7,8 @@ - + + diff --git a/src/MyService/MyServiceConfiguration.cs b/src/MyService/MyServiceConfiguration.cs index ee8f4278..03fa7917 100644 --- a/src/MyService/MyServiceConfiguration.cs +++ b/src/MyService/MyServiceConfiguration.cs @@ -14,11 +14,8 @@ public MyServiceConfiguration() this.UsePersistence(); this.EnableInstallers(); - //TODO: renable when a compatible combination of alphas is available - //var transport = new RabbitMQTransport(Topology.Conventional, "host=localhost;username=guest;password=guest"); - //var routing = this.UseTransport(transport); - - var routing = this.UseTransport(new LearningTransport()); + var transport = new RabbitMQTransport(Topology.Conventional, "host=localhost;username=guest;password=guest"); + var routing = this.UseTransport(transport); routing.RouteToEndpoint(typeof(AMessage), "MyOtherService"); diff --git a/src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj b/src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj index a4d32b94..52163de8 100644 --- a/src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj +++ b/src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj @@ -6,12 +6,12 @@ false - - + + - + diff --git a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs index 2e3cbc37..2c0afa3c 100644 --- a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs +++ b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs @@ -11,18 +11,17 @@ namespace MySystem.AcceptanceTests { public class When_sending_CompleteASaga { - //TODO: renable when a compatible combination of alphas is available - //[OneTimeSetUp] - //public async Task Setup() - //{ - // await DockerCompose.Up(); - //} + [OneTimeSetUp] + public async Task Setup() + { + await DockerCompose.Up(); + } - //[OneTimeTearDown] - //public void Teardown() - //{ - // DockerCompose.Down(); - //} + [OneTimeTearDown] + public void Teardown() + { + DockerCompose.Down(); + } [Test] public async Task ASaga_is_completed() diff --git a/src/NServiceBus.AssemblyScanner.Extensions/NServiceBus.AssemblyScanner.Extensions.csproj b/src/NServiceBus.AssemblyScanner.Extensions/NServiceBus.AssemblyScanner.Extensions.csproj index 954c5741..3d6771dd 100644 --- a/src/NServiceBus.AssemblyScanner.Extensions/NServiceBus.AssemblyScanner.Extensions.csproj +++ b/src/NServiceBus.AssemblyScanner.Extensions/NServiceBus.AssemblyScanner.Extensions.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8.csproj b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8.csproj index e045769c..2a48e3f7 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8.csproj +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/NServiceBus.IntegrationTesting.Tests.TestEndpoint/NServiceBus.IntegrationTesting.Tests.TestEndpoint.csproj b/src/NServiceBus.IntegrationTesting.Tests.TestEndpoint/NServiceBus.IntegrationTesting.Tests.TestEndpoint.csproj index d574bac0..583515bd 100644 --- a/src/NServiceBus.IntegrationTesting.Tests.TestEndpoint/NServiceBus.IntegrationTesting.Tests.TestEndpoint.csproj +++ b/src/NServiceBus.IntegrationTesting.Tests.TestEndpoint/NServiceBus.IntegrationTesting.Tests.TestEndpoint.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj b/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj index fa01d19f..d6163ac0 100644 --- a/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj +++ b/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj @@ -25,8 +25,8 @@ - - + + From ab6fbf162d1475ab1b91cf889ed2b125c4ec76c8 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 15:49:03 +0100 Subject: [PATCH 19/52] Missing reference --- src/Snippets/Snippets.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Snippets/Snippets.csproj b/src/Snippets/Snippets.csproj index aa72f3cc..5bf33759 100644 --- a/src/Snippets/Snippets.csproj +++ b/src/Snippets/Snippets.csproj @@ -11,6 +11,7 @@ + From 036c0a4ccdd36aae2d457a9316fc7c0415ad3c7a Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 16:26:06 +0100 Subject: [PATCH 20/52] formatting and usings --- .../EndpointStartupCallback.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs index 306c28c3..4d5f804f 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using NServiceBus.Features; -using System; using System.Threading; using System.Threading.Tasks; @@ -10,7 +9,7 @@ class EndpointStartupCallback : Feature { protected override void Setup(FeatureConfigurationContext context) { - context.RegisterStartupTask( container => + context.RegisterStartupTask(container => { var server = container.GetService(); return new CallbackStartupTask(server); From 3b2676984a20dcc9ead040c3bcda5995958d5255 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 16:28:32 +0100 Subject: [PATCH 21/52] Minor enhancements to remote endpoint client --- .../RemoteEndpointClient.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs index d3393185..4a0dd921 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs @@ -19,7 +19,7 @@ public RemoteEndpointClient() public async Task Stop() { //cannot await: stopping the remote process kills the server - //that kills the stream tat blows up the client if awaits + //that kills the stream that blows up the client if this awaits _ = client.StopAsync(new Google.Protobuf.WellKnownTypes.Empty()); await channel.ShutdownAsync(); } @@ -31,6 +31,10 @@ public async Task OnEndpointStarted(Func onStarted) { var current = response.ResponseStream.Current; await onStarted(current); + + //EndpointStarted is fired only once. Don't need to stay + //connected to this stream any longer + return; } } } From 104b748b5ffa22d95ee6b5df82de1f47e24b9721 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 16:28:42 +0100 Subject: [PATCH 22/52] typos --- .../RemoteEndpointImpl.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs index 5983888b..8660d3df 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs @@ -8,7 +8,7 @@ namespace NServiceBus.IntegrationTesting.OutOfProcess { class RemoteEndpointImpl : RemoteEndpointBase { - bool endpoitStarted; + bool endpointStarted; readonly string endpointName; public RemoteEndpointImpl(string endpointName) @@ -25,7 +25,7 @@ public override Task Stop(Empty request, ServerCallContext context) public override async Task EndpointStarted(Empty request, IServerStreamWriter responseStream, ServerCallContext context) { - while (!endpoitStarted) + while (!endpointStarted) { await Task.Delay(500); } @@ -38,7 +38,7 @@ await responseStream.WriteAsync(new EndpointStartedEvent() internal void NotifyEndpointStarted() { - endpoitStarted = true; + endpointStarted = true; } } } From ad6415fc804ab5035bab833370c4ac1d20b8869d Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 16:29:03 +0100 Subject: [PATCH 23/52] Not needed using directives --- .../RemoteEndpointServer.cs | 1 - .../OutOfProcessEndpointBehavior.cs | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs index d213bc7d..73b663a6 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs @@ -1,5 +1,4 @@ using Grpc.Core; -using System.Threading.Tasks; namespace NServiceBus.IntegrationTesting.OutOfProcess { diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs index bee43cbf..ecb7748b 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs @@ -1,10 +1,6 @@ -using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using NServiceBus.AcceptanceTesting.Support; namespace NServiceBus.IntegrationTesting From 3411aca294bcd8e67b374a87ac6d7dc484d4c625 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 16:29:34 +0100 Subject: [PATCH 24/52] Make start and stop of remote endpoints more robust --- .../OutOfProcessEndpointRunner.cs | 104 ++++++++++++------ 1 file changed, 73 insertions(+), 31 deletions(-) diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs index a33c42d2..24d6f041 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Grpc.Core; using NServiceBus.AcceptanceTesting.Support; using NServiceBus.IntegrationTesting.OutOfProcess; using NServiceBus.Logging; @@ -39,12 +40,12 @@ public override async Task Start(CancellationToken token) outputTask = process.StandardOutput.ReadToEndAsync(); errorTask = process.StandardError.ReadToEndAsync(); - - await ConnectToRemoteEndpoint(); + + await ConnectToRemoteEndpointWithRetries(); //build the remote session proxy IMessageSession messageSession = null; - + //TODO: How to access ScenarioContext.CurrentEndpoint // ScenarioContext.CurrentEndpoint = Name; try @@ -98,60 +99,101 @@ await Task.Run(async () => } } - private Task ConnectToRemoteEndpoint() + private async Task ConnectToRemoteEndpointWithRetries() { - return remoteEndpoint.OnEndpointStarted(e => + var connected = false; + + //TODO: should this be configurable? + var maxAttempts = 10; + var attempts = 0; + + while (!connected) { - Logger.Info($"Remote endpoint '{e.EndpointName}' started."); + try + { + Logger.Debug($"Connecting to remote endpoint: '{Name}' - Attempt {attempts + 1}"); + await remoteEndpoint.OnEndpointStarted(e => + { + Logger.Info($"Remote endpoint '{e.EndpointName}' started."); + + remoteEndpointStarted = true; + return Task.CompletedTask; + }); - remoteEndpointStarted = true; - return Task.CompletedTask; - }); + connected = true; + } + catch (Exception ex) when ((ex is RpcException rpcEx) && rpcEx.StatusCode == StatusCode.Unavailable) + { + attempts++; + if (attempts > maxAttempts) + { + Logger.Error($"Failed to connect to remote endpoint '{Name}' after {attempts + 1} attempts.", rpcEx); + throw; + } + + Logger.Debug($"Failed to connect to remote endpoint '{Name}', waiting to retry."); + //TODO: should this be configurable? + await Task.Delay(500); + } + } } public override async Task Stop() { //TODO: How to access ScenarioContext.CurrentEndpoint // ScenarioContext.CurrentEndpoint = Name; + + List errors = new(); try { - while (!remoteEndpointStarted) + if (!remoteEndpointStarted) { - await Task.Delay(500); + Logger.Warn($"Remote endpoint '{Name}' never published the started event. Killing the process."); + process.Kill(); } - - await remoteEndpoint.Stop(); - - var output = await outputTask; - var error = await errorTask; - - if (output != string.Empty) + else { - Console.WriteLine(output); + Logger.Info($"Attempt to gracefully shutdown remote endpoint '{Name}'."); + await remoteEndpoint.Stop(); } + } + catch (Exception ex) + { + Logger.Error($"Failed to stop remote endpoint {Name}.", ex); + errors.Add(ex); + } - if (error != string.Empty) - { - throw new Exception(error); - } + var output = await outputTask; + var error = await errorTask; - process.Dispose(); - process = null; + if (output != string.Empty) + { + Logger.Info(output); } - catch (Exception ex) + + if (error != string.Empty) { - Logger.Error("Failed to stop endpoint " + Name, ex); - throw; + Logger.Error(error); + errors.Add(new Exception(error)); } - ThrowOnFailedMessages(); + process.Dispose(); + process = null; + + errors.AddRange(GetFailedMessagesExceptions()); + + if (errors.Any()) + { + throw new AggregateException(errors.ToArray()); + } } - void ThrowOnFailedMessages() + IEnumerable GetFailedMessagesExceptions() { foreach (var failedMessage in runDescriptor.ScenarioContext.FailedMessages.Where(kvp => kvp.Key == Name)) { - throw new MessageFailedException(failedMessage.Value.First(), runDescriptor.ScenarioContext); + Logger.Error($"Message failed: {failedMessage.Value.First()}"); + yield return new MessageFailedException(failedMessage.Value.First(), runDescriptor.ScenarioContext); } } } From 19007e59c662004016c09bdf623f4d5d570e3b5b Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Jan 2022 16:40:34 +0100 Subject: [PATCH 25/52] typos --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d7d4cb8b..fb2c352a 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ It's important to make sure that each endpoint configuration works as expected, ### Testing message routing -[Message routing](https://docs.particular.net/nservicebus/messaging/routing) is part of the endpoint configuration and is what makes a choreography successful, the only way to test correctness of the message routing set up is to exercise the choreography so that endpoints will use the production routing configuration to exchange messages, and to subscribe to events. If the routing configuration is wrong or is missing pieces the choreography tests wil fail. +[Message routing](https://docs.particular.net/nservicebus/messaging/routing) is part of the endpoint configuration and is what makes a choreography successful, the only way to test correctness of the message routing set up is to exercise the choreography so that endpoints will use the production routing configuration to exchange messages, and to subscribe to events. If the routing configuration is wrong or is missing pieces the choreography tests will fail. ### Testing saga message mappings @@ -85,18 +85,18 @@ In theory, it's possible to assert on [saga message mappings](https://docs.parti Defining an NServiceBus integration test is a multi-step process, composed of: -- Make sure endpoints configuration can be istantiated by tests +- Make sure endpoints configuration can be instantiated by tests - Define endpoints used in each test; and if needed customize each endpoint configuration to adapt to the test environment - Define tests and completion criteria - Assert on tests results -### Make sure endpoints configuration can be istantiated by tests +### Make sure endpoints configuration can be instantiated by tests One of the goals of end-to-end testing a NServiceBus endpoint is to make sure that what gets tested is the real production code, not a copy of it crafted for the tests. The production endpoint configuration has to be used in tests. To make sure that the testing infrastructure can instantiate the endpoint configuration there are a couple of options, with many variations. #### Inherit from EndpointConfiguration -It's possible to create a class that inherits from `EndpointConfiguration` and then use it in both the production endpoint and the tests. To make so that the testing infrastructure can automatically instantiate it, the class must have a parameterless constructor, like in the following snippet: +It's possible to create a class that inherits from `EndpointConfiguration` and then use it in both the production endpoint and the tests. To make so that the testing infrastructure can automatically instantiate it, the class must have a parameter-less constructor, like in the following snippet: @@ -310,7 +310,7 @@ An end-to-end test execution can only be terminated by 3 events: - the test times out - there are unhandled exceptions -Unhandled exceptions are a sort of problem from the integration testing infrastructure perspecive as most of the times they'll result in messages being retried and eventually ending up in the error queue. Based on this it's better to consider failed messages as part of the done condition: +Unhandled exceptions are a sort of problem from the integration testing infrastructure perspective as most of the times they'll result in messages being retried and eventually ending up in the error queue. Based on this it's better to consider failed messages as part of the done condition: @@ -323,7 +323,7 @@ Unhandled exceptions are a sort of problem from the integration testing infrastr snippet source | anchor -Such a done condition has to be read as: "If there are one or more failed messages the test is done, proceed to evaulate the assertions". Obviously this is not enough. In the identified test case scenario the test is done when a saga is invoked (specifically is created, more on this later). A saga invokation can be expressed as a done condition in the following way: +Such a done condition has to be read as: "If there are one or more failed messages the test is done, proceed to evaluate the assertions". Obviously this is not enough. In the identified test case scenario the test is done when a saga is invoked (specifically is created, more on this later). A saga invocation can be expressed as a done condition in the following way: @@ -453,7 +453,7 @@ The above extension method is far from being complete and doesn't handle all the ## How to install -Using a package manager add a nuget reference to [NServiceBus.IntegrationTesting](https://www.nuget.org/packages/NServiceBus.IntegrationTesting/). +Using a package manager add a NuGet reference to [NServiceBus.IntegrationTesting](https://www.nuget.org/packages/NServiceBus.IntegrationTesting/). ## Background From dbf56f9afd6398c96c41b9d5a33c3be8c206a3e4 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 7 Jan 2022 04:50:43 +0100 Subject: [PATCH 26/52] Respect the default endpoints startup timeout --- .../OutOfProcessEndpointRunner.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs index 24d6f041..df14b950 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs @@ -103,8 +103,9 @@ private async Task ConnectToRemoteEndpointWithRetries() { var connected = false; - //TODO: should this be configurable? - var maxAttempts = 10; + //NServiceBus ATT comes with a default hardcoded 2 minutes endpoints startup timeout + var maxAttempts = 240; + var msDelayBetweenAttempts = 500; var attempts = 0; while (!connected) @@ -132,8 +133,7 @@ await remoteEndpoint.OnEndpointStarted(e => } Logger.Debug($"Failed to connect to remote endpoint '{Name}', waiting to retry."); - //TODO: should this be configurable? - await Task.Delay(500); + await Task.Delay(msDelayBetweenAttempts); } } } From 9b90c24a9720e2b4262f3c881da574a2f24dd0d0 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 7 Jan 2022 04:55:33 +0100 Subject: [PATCH 27/52] Wait before whens execution, some more logging --- .../OutOfProcessEndpointRunner.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs index df14b950..3cf652a9 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs @@ -43,11 +43,20 @@ public override async Task Start(CancellationToken token) await ConnectToRemoteEndpointWithRetries(); - //build the remote session proxy - IMessageSession messageSession = null; + while (!remoteEndpointStarted) + { + Logger.Debug($"Waiting for remote endpoint '{Name}' to start."); + await Task.Delay(100); + } + + Logger.Info($"Remote endpoint '{Name}' started. Executing whens."); //TODO: How to access ScenarioContext.CurrentEndpoint // ScenarioContext.CurrentEndpoint = Name; + + //build the remote session proxy + IMessageSession messageSession = null; + try { if (whens.Count != 0) @@ -93,7 +102,7 @@ await Task.Run(async () => } catch (Exception ex) { - Logger.Error($"Failed to execute Whens on endpoint{Name}", ex); + Logger.Error($"Failed to execute Whens on endpoint {Name}", ex); throw; } @@ -136,6 +145,8 @@ await remoteEndpoint.OnEndpointStarted(e => await Task.Delay(msDelayBetweenAttempts); } } + + Logger.Debug($"Connected to remote endpoint: '{Name}'."); } public override async Task Stop() From e655441b0b70c5d8356c2e737d806b516cd5e086 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 7 Jan 2022 05:17:05 +0100 Subject: [PATCH 28/52] Required infrastructure for remote whens execution --- .../When_sending_CompleteASaga.cs | 15 +++++- .../IRemoteEndpointWhenDefinition.cs | 12 +++++ .../IRemoteMessageSessionProxy.cs | 7 +++ .../OutOfProcessEndpointBehavior.cs | 9 ++-- .../OutOfProcessEndpointBehaviorBuilder.cs | 24 ++++----- .../OutOfProcessEndpointRunner.cs | 16 +++--- .../RemoteEndpointWhenDefinition.cs | 49 +++++++++++++++++++ 7 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 src/NServiceBus.IntegrationTesting/IRemoteEndpointWhenDefinition.cs create mode 100644 src/NServiceBus.IntegrationTesting/IRemoteMessageSessionProxy.cs create mode 100644 src/NServiceBus.IntegrationTesting/RemoteEndpointWhenDefinition.cs diff --git a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs index 2c0afa3c..029244a0 100644 --- a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs +++ b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs @@ -23,13 +23,23 @@ public void Teardown() DockerCompose.Down(); } + class Context : IntegrationScenarioContext + { + public bool WhenExecuted { get; set; } + } + [Test] public async Task ASaga_is_completed() { var theExpectedIdentifier = Guid.NewGuid(); - var context = await Scenario.Define() + var context = await Scenario.Define() .WithOutOfProcessEndpoint("MyService", EndpointReference.FromSolutionProject("MyService.TestProxy"), behavior => { + behavior.When((remoteSession, ctx) => + { + ctx.WhenExecuted = true; + return Task.CompletedTask; + }); //behavior.When(session => //{ // return session.Send("MyService", new StartASaga() {AnIdentifier = theExpectedIdentifier}); @@ -45,7 +55,7 @@ public async Task ASaga_is_completed() }) .Done(ctx => { - return true; + return ctx.WhenExecuted; //return ctx.HasFailedMessages() || ctx.InvokedSagas.Any(s => s.SagaType == typeof(ASaga) && s.IsCompleted); }) .Run(); @@ -56,6 +66,7 @@ public async Task ASaga_is_completed() //Assert.IsNotNull(newSaga); //Assert.IsNotNull(completedSaga); + Assert.True(context.WhenExecuted); Assert.False(context.HasFailedMessages()); Assert.False(context.HasHandlingErrors()); } diff --git a/src/NServiceBus.IntegrationTesting/IRemoteEndpointWhenDefinition.cs b/src/NServiceBus.IntegrationTesting/IRemoteEndpointWhenDefinition.cs new file mode 100644 index 00000000..00a4793a --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/IRemoteEndpointWhenDefinition.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting +{ + public interface IRemoteEndpointWhenDefinition + { + Guid Id { get; } + + Task ExecuteAction(IntegrationScenarioContext context, IRemoteMessageSessionProxy session); + } +} \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting/IRemoteMessageSessionProxy.cs b/src/NServiceBus.IntegrationTesting/IRemoteMessageSessionProxy.cs new file mode 100644 index 00000000..5d7fb6e0 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/IRemoteMessageSessionProxy.cs @@ -0,0 +1,7 @@ +namespace NServiceBus.IntegrationTesting +{ + public interface IRemoteMessageSessionProxy + { + + } +} \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs index ecb7748b..d1168624 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; @@ -8,14 +9,14 @@ namespace NServiceBus.IntegrationTesting class OutOfProcessEndpointBehavior : IComponentBehavior { readonly EndpointReference reference; - readonly IList whens; + readonly IList remoteWhens; readonly string endpointName; - public OutOfProcessEndpointBehavior(string endpointName, EndpointReference reference, IList whens) + public OutOfProcessEndpointBehavior(string endpointName, EndpointReference reference, IList remoteWhens) { this.endpointName = endpointName; this.reference = reference; - this.whens = whens; + this.remoteWhens = remoteWhens; } public Task CreateRunner(RunDescriptor runDescriptor) @@ -30,7 +31,7 @@ public Task CreateRunner(RunDescriptor runDescriptor) process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; process.StartInfo.CreateNoWindow = true; - var runner = new OutOfProcessEndpointRunner(runDescriptor, endpointName, process, whens); + var runner = new OutOfProcessEndpointRunner(runDescriptor, endpointName, process, remoteWhens); return Task.FromResult((ComponentRunner)runner); } } diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehaviorBuilder.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehaviorBuilder.cs index 56c35e8a..dfa570bf 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehaviorBuilder.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehaviorBuilder.cs @@ -6,46 +6,46 @@ namespace NServiceBus.IntegrationTesting { - public class OutOfProcessEndpointBehaviorBuilder where TContext : ScenarioContext + public class OutOfProcessEndpointBehaviorBuilder where TContext : IntegrationScenarioContext { - public OutOfProcessEndpointBehaviorBuilder When(Func action) + public OutOfProcessEndpointBehaviorBuilder When(Func action) { return When(c => true, action); } - public OutOfProcessEndpointBehaviorBuilder When(Func action) + public OutOfProcessEndpointBehaviorBuilder When(Func action) { return When(c => true, action); } - public OutOfProcessEndpointBehaviorBuilder When(Func> condition, Func action) + public OutOfProcessEndpointBehaviorBuilder When(Func> condition, Func action) { - Whens.Add(new WhenDefinition(condition, action)); + Whens.Add(new RemoteEndpointWhenDefinition(condition, action)); return this; } - public OutOfProcessEndpointBehaviorBuilder When(Predicate condition, Func action) + public OutOfProcessEndpointBehaviorBuilder When(Predicate condition, Func action) { - Whens.Add(new WhenDefinition(ctx => Task.FromResult(condition(ctx)), action)); + Whens.Add(new RemoteEndpointWhenDefinition(ctx => Task.FromResult(condition(ctx)), action)); return this; } - public OutOfProcessEndpointBehaviorBuilder When(Func> condition, Func action) + public OutOfProcessEndpointBehaviorBuilder When(Func> condition, Func action) { - Whens.Add(new WhenDefinition(condition, action)); + Whens.Add(new RemoteEndpointWhenDefinition(condition, action)); return this; } - public OutOfProcessEndpointBehaviorBuilder When(Predicate condition, Func action) + public OutOfProcessEndpointBehaviorBuilder When(Predicate condition, Func action) { - Whens.Add(new WhenDefinition(ctx => Task.FromResult(condition(ctx)), action)); + Whens.Add(new RemoteEndpointWhenDefinition(ctx => Task.FromResult(condition(ctx)), action)); return this; } - public IList Whens { get; } = new List(); + public IList Whens { get; } = new List(); } } \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs index 3cf652a9..96531f86 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs @@ -21,15 +21,15 @@ class OutOfProcessEndpointRunner : ComponentRunner private Task errorTask; readonly RunDescriptor runDescriptor; bool remoteEndpointStarted; - readonly IList whens; + readonly IList remoteWhens; - public OutOfProcessEndpointRunner(RunDescriptor runDescriptor, string endpointName, Process process, IList whens) + public OutOfProcessEndpointRunner(RunDescriptor runDescriptor, string endpointName, Process process, IList remoteWhens) { remoteEndpoint = new RemoteEndpointClient(); Name = endpointName; this.runDescriptor = runDescriptor; this.process = process; - this.whens = whens; + this.remoteWhens = remoteWhens; } public override string Name { get; } @@ -55,11 +55,11 @@ public override async Task Start(CancellationToken token) // ScenarioContext.CurrentEndpoint = Name; //build the remote session proxy - IMessageSession messageSession = null; + IRemoteMessageSessionProxy messageSessionProxy = null; try { - if (whens.Count != 0) + if (remoteWhens.Count != 0) { await Task.Run(async () => { @@ -67,7 +67,7 @@ await Task.Run(async () => while (!token.IsCancellationRequested) { - if (executedWhens.Count == whens.Count) + if (executedWhens.Count == remoteWhens.Count) { break; } @@ -77,7 +77,7 @@ await Task.Run(async () => break; } - foreach (var when in whens) + foreach (var when in remoteWhens) { if (token.IsCancellationRequested) { @@ -89,7 +89,7 @@ await Task.Run(async () => continue; } - if (await when.ExecuteAction(runDescriptor.ScenarioContext, messageSession).ConfigureAwait(false)) + if (await when.ExecuteAction((IntegrationScenarioContext)runDescriptor.ScenarioContext, messageSessionProxy).ConfigureAwait(false)) { executedWhens.Add(when.Id); } diff --git a/src/NServiceBus.IntegrationTesting/RemoteEndpointWhenDefinition.cs b/src/NServiceBus.IntegrationTesting/RemoteEndpointWhenDefinition.cs new file mode 100644 index 00000000..d3c5db52 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/RemoteEndpointWhenDefinition.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting +{ + class RemoteEndpointWhenDefinition : IRemoteEndpointWhenDefinition where TContext : IntegrationScenarioContext + { + public RemoteEndpointWhenDefinition(Func> condition, Func action) + { + Id = Guid.NewGuid(); + this.condition = condition; + messageAction = action; + } + + public RemoteEndpointWhenDefinition(Func> condition, Func actionWithContext) + { + Id = Guid.NewGuid(); + this.condition = condition; + messageAndContextAction = actionWithContext; + } + + public Guid Id { get; } + + public async Task ExecuteAction(IntegrationScenarioContext context, IRemoteMessageSessionProxy session) + { + var c = (TContext)context; + + if (!await condition(c).ConfigureAwait(false)) + { + return false; + } + + if (messageAction != null) + { + await messageAction(session).ConfigureAwait(false); + } + else + { + await messageAndContextAction(session, c).ConfigureAwait(false); + } + + return true; + } + + Func> condition; + Func messageAction; + Func messageAndContextAction; + } +} \ No newline at end of file From e8b701d543ac4967717ee62c2dd3bfc0b0fe6611 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 7 Jan 2022 05:19:27 +0100 Subject: [PATCH 29/52] Approved API --- .../APIApprovals.Approve_API.approved.txt | 70 ++++++------------- .../APIApprovals.Approve_API.approved.txt | 70 ++++++------------- 2 files changed, 44 insertions(+), 96 deletions(-) diff --git a/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt b/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt index e36b1e62..6a5d2874 100644 --- a/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt +++ b/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt @@ -11,6 +11,10 @@ namespace NServiceBus.IntegrationTesting public static void RegisterRequiredPipelineBehaviors(this NServiceBus.EndpointConfiguration builder, string endpointName, NServiceBus.IntegrationTesting.IntegrationScenarioContext integrationScenarioContext) { } public static void RegisterScenarioContext(this NServiceBus.EndpointConfiguration builder, NServiceBus.AcceptanceTesting.ScenarioContext scenarioContext) { } } + public class EndpointReference + { + public static NServiceBus.IntegrationTesting.EndpointReference FromSolutionProject(string projectName) { } + } public abstract class EndpointTemplate : NServiceBus.AcceptanceTesting.Support.IEndpointSetupTemplate { protected EndpointTemplate() { } @@ -42,15 +46,16 @@ namespace NServiceBus.IntegrationTesting public NServiceBus.IntegrationTesting.GenericHostEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } public NServiceBus.IntegrationTesting.GenericHostEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } } - public class HandlerInvocation : NServiceBus.IntegrationTesting.Invocation - { - public HandlerInvocation() { } - public System.Type HandlerType { get; } - } public interface IHandleTestCompletion { System.Threading.Tasks.Task OnTestCompleted(NServiceBus.AcceptanceTesting.Support.RunSummary summary); } + public interface IRemoteEndpointWhenDefinition + { + System.Guid Id { get; } + System.Threading.Tasks.Task ExecuteAction(NServiceBus.IntegrationTesting.IntegrationScenarioContext context, NServiceBus.IntegrationTesting.IRemoteMessageSessionProxy session); + } + public interface IRemoteMessageSessionProxy { } public class IntegrationScenarioContext : NServiceBus.AcceptanceTesting.ScenarioContext { public IntegrationScenarioContext() { } @@ -69,54 +74,23 @@ namespace NServiceBus.IntegrationTesting public bool SagaWasInvoked() where TSaga : NServiceBus.Saga { } } - public abstract class Invocation - { - protected Invocation() { } - public string EndpointName { get; } - public System.Exception HandlingError { get; } - public object Message { get; } - public System.Type MessageType { get; } - } - public abstract class OutgoingMessageOperation - { - protected OutgoingMessageOperation() { } - public System.Collections.Generic.Dictionary MessageHeaders { get; } - public string MessageId { get; } - public object MessageInstance { get; } - public System.Type MessageType { get; } - public System.Exception OperationError { get; } - public string SenderEndpoint { get; } - } - public class PublishOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation - { - public PublishOperation() { } - } - public class ReplyOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation - { - public ReplyOperation() { } - } - public class RequestTimeoutOperation : NServiceBus.IntegrationTesting.SendOperation + public class OutOfProcessEndpointBehaviorBuilder + where TContext : NServiceBus.IntegrationTesting.IntegrationScenarioContext { - public RequestTimeoutOperation() { } - public string SagaId { get; } - public string SagaTypeAssemblyQualifiedName { get; } - } - public class SagaInvocation : NServiceBus.IntegrationTesting.Invocation - { - public SagaInvocation() { } - public bool IsCompleted { get; } - public bool IsNew { get; } - public bool NotFound { get; } - public NServiceBus.IContainSagaData SagaData { get; } - public System.Type SagaType { get; } + public OutOfProcessEndpointBehaviorBuilder() { } + public System.Collections.Generic.IList Whens { get; } + public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Func action) { } + public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Func action) { } + public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Func> condition, System.Func action) { } + public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Func> condition, System.Func action) { } + public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } + public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } } public static class ScenarioWithEndpointBehaviorExtensions { public static NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior WithGenericHostEndpoint(this NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior scenarioWithEndpoint, string endpointName, System.Func, Microsoft.Extensions.Hosting.IHost> hostBuilder, System.Action> behavior = null) where TContext : NServiceBus.AcceptanceTesting.ScenarioContext { } - } - public class SendOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation - { - public SendOperation() { } + public static NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior WithOutOfProcessEndpoint(this NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior scenarioWithEndpoint, string endpointName, NServiceBus.IntegrationTesting.EndpointReference endpointReference, System.Action> behavior = null) + where TContext : NServiceBus.IntegrationTesting.IntegrationScenarioContext { } } } \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt b/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt index e36b1e62..6a5d2874 100644 --- a/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt +++ b/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt @@ -11,6 +11,10 @@ namespace NServiceBus.IntegrationTesting public static void RegisterRequiredPipelineBehaviors(this NServiceBus.EndpointConfiguration builder, string endpointName, NServiceBus.IntegrationTesting.IntegrationScenarioContext integrationScenarioContext) { } public static void RegisterScenarioContext(this NServiceBus.EndpointConfiguration builder, NServiceBus.AcceptanceTesting.ScenarioContext scenarioContext) { } } + public class EndpointReference + { + public static NServiceBus.IntegrationTesting.EndpointReference FromSolutionProject(string projectName) { } + } public abstract class EndpointTemplate : NServiceBus.AcceptanceTesting.Support.IEndpointSetupTemplate { protected EndpointTemplate() { } @@ -42,15 +46,16 @@ namespace NServiceBus.IntegrationTesting public NServiceBus.IntegrationTesting.GenericHostEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } public NServiceBus.IntegrationTesting.GenericHostEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } } - public class HandlerInvocation : NServiceBus.IntegrationTesting.Invocation - { - public HandlerInvocation() { } - public System.Type HandlerType { get; } - } public interface IHandleTestCompletion { System.Threading.Tasks.Task OnTestCompleted(NServiceBus.AcceptanceTesting.Support.RunSummary summary); } + public interface IRemoteEndpointWhenDefinition + { + System.Guid Id { get; } + System.Threading.Tasks.Task ExecuteAction(NServiceBus.IntegrationTesting.IntegrationScenarioContext context, NServiceBus.IntegrationTesting.IRemoteMessageSessionProxy session); + } + public interface IRemoteMessageSessionProxy { } public class IntegrationScenarioContext : NServiceBus.AcceptanceTesting.ScenarioContext { public IntegrationScenarioContext() { } @@ -69,54 +74,23 @@ namespace NServiceBus.IntegrationTesting public bool SagaWasInvoked() where TSaga : NServiceBus.Saga { } } - public abstract class Invocation - { - protected Invocation() { } - public string EndpointName { get; } - public System.Exception HandlingError { get; } - public object Message { get; } - public System.Type MessageType { get; } - } - public abstract class OutgoingMessageOperation - { - protected OutgoingMessageOperation() { } - public System.Collections.Generic.Dictionary MessageHeaders { get; } - public string MessageId { get; } - public object MessageInstance { get; } - public System.Type MessageType { get; } - public System.Exception OperationError { get; } - public string SenderEndpoint { get; } - } - public class PublishOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation - { - public PublishOperation() { } - } - public class ReplyOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation - { - public ReplyOperation() { } - } - public class RequestTimeoutOperation : NServiceBus.IntegrationTesting.SendOperation + public class OutOfProcessEndpointBehaviorBuilder + where TContext : NServiceBus.IntegrationTesting.IntegrationScenarioContext { - public RequestTimeoutOperation() { } - public string SagaId { get; } - public string SagaTypeAssemblyQualifiedName { get; } - } - public class SagaInvocation : NServiceBus.IntegrationTesting.Invocation - { - public SagaInvocation() { } - public bool IsCompleted { get; } - public bool IsNew { get; } - public bool NotFound { get; } - public NServiceBus.IContainSagaData SagaData { get; } - public System.Type SagaType { get; } + public OutOfProcessEndpointBehaviorBuilder() { } + public System.Collections.Generic.IList Whens { get; } + public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Func action) { } + public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Func action) { } + public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Func> condition, System.Func action) { } + public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Func> condition, System.Func action) { } + public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } + public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } } public static class ScenarioWithEndpointBehaviorExtensions { public static NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior WithGenericHostEndpoint(this NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior scenarioWithEndpoint, string endpointName, System.Func, Microsoft.Extensions.Hosting.IHost> hostBuilder, System.Action> behavior = null) where TContext : NServiceBus.AcceptanceTesting.ScenarioContext { } - } - public class SendOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation - { - public SendOperation() { } + public static NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior WithOutOfProcessEndpoint(this NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior scenarioWithEndpoint, string endpointName, NServiceBus.IntegrationTesting.EndpointReference endpointReference, System.Action> behavior = null) + where TContext : NServiceBus.IntegrationTesting.IntegrationScenarioContext { } } } \ No newline at end of file From 80cbad661c4928b64f67290bca10fdd04b0d2311 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 7 Jan 2022 05:38:53 +0100 Subject: [PATCH 30/52] in process and out of process in the same test --- .../MyOtherService.TestProxy.csproj | 14 ++ src/MyOtherService.TestProxy/Program.cs | 15 ++ .../When_sending_AMessage.cs | 138 ++++++++++-------- 3 files changed, 110 insertions(+), 57 deletions(-) create mode 100644 src/MyOtherService.TestProxy/MyOtherService.TestProxy.csproj create mode 100644 src/MyOtherService.TestProxy/Program.cs diff --git a/src/MyOtherService.TestProxy/MyOtherService.TestProxy.csproj b/src/MyOtherService.TestProxy/MyOtherService.TestProxy.csproj new file mode 100644 index 00000000..11896633 --- /dev/null +++ b/src/MyOtherService.TestProxy/MyOtherService.TestProxy.csproj @@ -0,0 +1,14 @@ + + + + Exe + net48 + latest + + + + + + + + diff --git a/src/MyOtherService.TestProxy/Program.cs b/src/MyOtherService.TestProxy/Program.cs new file mode 100644 index 00000000..d548cbb2 --- /dev/null +++ b/src/MyOtherService.TestProxy/Program.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Extensions.Hosting; +using NServiceBus; +using Serilog; + +namespace MyOtherService.TestProxy +{ + public class Program + { + public static void Main(string[] args) + { + MyOtherService.Program.CreateHostBuilder(args).Build().Run(); + } + } +} \ No newline at end of file diff --git a/src/MySystem.AcceptanceTests/When_sending_AMessage.cs b/src/MySystem.AcceptanceTests/When_sending_AMessage.cs index ab1dbfab..8f45065c 100644 --- a/src/MySystem.AcceptanceTests/When_sending_AMessage.cs +++ b/src/MySystem.AcceptanceTests/When_sending_AMessage.cs @@ -1,57 +1,81 @@ -//using MyMessages.Messages; -//using NServiceBus; -//using NServiceBus.AcceptanceTesting; -//using NServiceBus.IntegrationTesting; -//using NUnit.Framework; -//using System; -//using System.Linq; -//using System.Threading.Tasks; - -//namespace MySystem.AcceptanceTests -//{ -// public class When_sending_AMessage -// { -// //TODO: renable when a compatible combination of alphas is available -// //[OneTimeSetUp] -// //public async Task Setup() -// //{ -// // await DockerCompose.Up(); -// //} - -// //[OneTimeTearDown] -// //public void Teardown() -// //{ -// // DockerCompose.Down(); -// //} - -// [Test] -// public async Task AReplyMessage_is_received_and_ASaga_is_started() -// { -// var theExpectedIdentifier = Guid.NewGuid(); -// var context = await Scenario.Define() -// .WithGenericHostEndpoint("MyService", configPreview => Program.CreateHostBuilder(new string[0], configPreview).Build(), behavior => -// { -// behavior.When(session => session.Send(new AMessage() {AnIdentifier = theExpectedIdentifier})); -// }) -// .WithEndpoint() -// .Done(c => c.SagaWasInvoked() || c.HasFailedMessages()) -// .Run(); - -// var invokedSaga = context.InvokedSagas.Single(s => s.SagaType == typeof(ASaga)); - -// Assert.True(invokedSaga.IsNew); -// Assert.AreEqual("MyService", invokedSaga.EndpointName); -// Assert.True(((ASagaData)invokedSaga.SagaData).AnIdentifier == theExpectedIdentifier); -// Assert.False(context.HasFailedMessages()); -// Assert.False(context.HasHandlingErrors()); -// } - -// class MyOtherServiceEndpoint : EndpointConfigurationBuilder -// { -// public MyOtherServiceEndpoint() -// { -// EndpointSetup(); -// } -// } -// } -//} +using MyMessages.Messages; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NServiceBus.IntegrationTesting; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace MySystem.AcceptanceTests +{ + public class When_sending_AMessage + { + [OneTimeSetUp] + public async Task Setup() + { + await DockerCompose.Up(); + } + + [OneTimeTearDown] + public void Teardown() + { + DockerCompose.Down(); + } + + class Context : IntegrationScenarioContext + { + public bool RemoteWhenExecuted { get; set; } + public bool InProcessWhenExecuted { get; set; } + } + + [Test] + public async Task AReplyMessage_is_received_and_ASaga_is_started() + { + var theExpectedIdentifier = Guid.NewGuid(); + var context = await Scenario.Define() + .WithOutOfProcessEndpoint("MyService", EndpointReference.FromSolutionProject("MyService.TestProxy"), behavior => + { + behavior.When((remoteSession, ctx) => + { + ctx.RemoteWhenExecuted = true; + return Task.CompletedTask; + }); + //behavior.When(session => session.Send(new AMessage() { AnIdentifier = theExpectedIdentifier })); + }) + .WithEndpoint(behavior => + { + behavior.When((session, ctx) => + { + ctx.InProcessWhenExecuted = true; + return Task.CompletedTask; + }); + }) + .Done(c => + { + return c.RemoteWhenExecuted && c.InProcessWhenExecuted; + //return c.SagaWasInvoked() || c.HasFailedMessages(); + }) + .Run(); + + Assert.True(context.RemoteWhenExecuted); + Assert.True(context.InProcessWhenExecuted); + + //var invokedSaga = context.InvokedSagas.Single(s => s.SagaType == typeof(ASaga)); + + //Assert.True(invokedSaga.IsNew); + //Assert.AreEqual("MyService", invokedSaga.EndpointName); + //Assert.True(((ASagaData)invokedSaga.SagaData).AnIdentifier == theExpectedIdentifier); + Assert.False(context.HasFailedMessages()); + Assert.False(context.HasHandlingErrors()); + } + + class MyOtherServiceEndpoint : EndpointConfigurationBuilder + { + public MyOtherServiceEndpoint() + { + EndpointSetup(); + } + } + } +} From cf5db62a837b9d494afbe22eccaf1ef02c26f353 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 7 Jan 2022 05:41:57 +0100 Subject: [PATCH 31/52] not used proto --- src/protos/RemoteEndpoint.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/src/protos/RemoteEndpoint.proto b/src/protos/RemoteEndpoint.proto index 78be59af..e9af212e 100644 --- a/src/protos/RemoteEndpoint.proto +++ b/src/protos/RemoteEndpoint.proto @@ -1,7 +1,6 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; -import "google/protobuf/timestamp.proto"; import "google/protobuf/any.proto"; option csharp_namespace = "NServiceBus.IntegrationTesting.OutOfProcess"; From 1937dec336cb1e67bb6ed09d1ebd3d3a21035ae1 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 7 Jan 2022 09:11:03 +0100 Subject: [PATCH 32/52] exclude tests that cannot run on Linux and multi-target tests --- src/MySystem.AcceptanceTests/MyOtherServiceTemplate.cs | 6 +++++- .../MySystem.AcceptanceTests.csproj | 6 +++--- src/MySystem.AcceptanceTests/When_sending_AMessage.cs | 7 ++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/MySystem.AcceptanceTests/MyOtherServiceTemplate.cs b/src/MySystem.AcceptanceTests/MyOtherServiceTemplate.cs index 222d87cb..e365f3dc 100644 --- a/src/MySystem.AcceptanceTests/MyOtherServiceTemplate.cs +++ b/src/MySystem.AcceptanceTests/MyOtherServiceTemplate.cs @@ -1,4 +1,6 @@ -using MyOtherService; +#if NET48_OR_GREATER + +using MyOtherService; using NServiceBus; using NServiceBus.AcceptanceTesting.Support; using NServiceBus.IntegrationTesting; @@ -18,3 +20,5 @@ protected override Task OnGetConfiguration(RunDescriptor } } } + +#endif \ No newline at end of file diff --git a/src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj b/src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj index 52163de8..bd7eb3b1 100644 --- a/src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj +++ b/src/MySystem.AcceptanceTests/MySystem.AcceptanceTests.csproj @@ -1,8 +1,7 @@  - net48 - + netcoreapp3.1;net48 false @@ -17,10 +16,11 @@ + - + diff --git a/src/MySystem.AcceptanceTests/When_sending_AMessage.cs b/src/MySystem.AcceptanceTests/When_sending_AMessage.cs index 8f45065c..24e62cd3 100644 --- a/src/MySystem.AcceptanceTests/When_sending_AMessage.cs +++ b/src/MySystem.AcceptanceTests/When_sending_AMessage.cs @@ -1,10 +1,9 @@ -using MyMessages.Messages; -using NServiceBus; +#if NET48_OR_GREATER + using NServiceBus.AcceptanceTesting; using NServiceBus.IntegrationTesting; using NUnit.Framework; using System; -using System.Linq; using System.Threading.Tasks; namespace MySystem.AcceptanceTests @@ -79,3 +78,5 @@ public MyOtherServiceEndpoint() } } } + +#endif \ No newline at end of file From 75f77b631f8cb848be018e42af3ed796512f5491 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 7 Jan 2022 09:25:16 +0100 Subject: [PATCH 33/52] More logging --- .../OutOfProcessEndpointRunner.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs index 96531f86..b1341f80 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs @@ -124,7 +124,7 @@ private async Task ConnectToRemoteEndpointWithRetries() Logger.Debug($"Connecting to remote endpoint: '{Name}' - Attempt {attempts + 1}"); await remoteEndpoint.OnEndpointStarted(e => { - Logger.Info($"Remote endpoint '{e.EndpointName}' started."); + Logger.Info($"Received EndpointStarted event from remote endpoint '{e.EndpointName}'."); remoteEndpointStarted = true; return Task.CompletedTask; @@ -151,6 +151,7 @@ await remoteEndpoint.OnEndpointStarted(e => public override async Task Stop() { + Logger.Debug($"OutOfProcess runner for remote endpoint '{Name}' stop requested."); //TODO: How to access ScenarioContext.CurrentEndpoint // ScenarioContext.CurrentEndpoint = Name; @@ -161,11 +162,13 @@ public override async Task Stop() { Logger.Warn($"Remote endpoint '{Name}' never published the started event. Killing the process."); process.Kill(); + Logger.Warn($"Remote endpoint '{Name}' killed."); } else { Logger.Info($"Attempt to gracefully shutdown remote endpoint '{Name}'."); await remoteEndpoint.Stop(); + Logger.Info($"Remote endpoint '{Name}' gracefully shutdown."); } } catch (Exception ex) @@ -197,6 +200,8 @@ public override async Task Stop() { throw new AggregateException(errors.ToArray()); } + + Logger.Debug($"OutOfProcess runner for remote endpoint '{Name}' stop completed."); } IEnumerable GetFailedMessagesExceptions() From 6744303d8321525f1c07e7b2543370e71bf5e59e Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 7 Jan 2022 16:23:46 +0100 Subject: [PATCH 34/52] Refactor to stop using streams and first failed attempt at remote send Instead of using streams the out of process test runner exposes a gRpc server and each remote endpoint connects to it --- src/MyMessages/Messages/ALonleyMessage.cs | 11 ++ src/MyService/ALonleyMessageHandler.cs | 14 +++ .../When_sending_ALonleyMessage.cs | 46 ++++++++ .../When_sending_AMessage.cs | 3 +- .../When_sending_CompleteASaga.cs | 4 +- .../AutoConfiguration.cs | 11 +- .../EndpointStartupCallback.cs | 17 +-- .../InterceptSendOperations.cs | 9 +- .../RemoteEndpointServerV8.cs | 26 ++++- .../IRemoteMessageSessionProxy.cs | 10 ++ .../IntegrationScenarioContextClient.cs | 12 -- ...Bus.IntegrationTesting.OutOfProcess.csproj | 27 +++-- .../RemoteEndpointClient.cs | 25 ++-- .../RemoteEndpointImpl.cs | 24 +--- .../RemoteEndpointServer.cs | 19 +-- .../TestRunnerClient.cs | 39 +++++++ .../TestRunnerImpl.cs | 38 ++++++ .../TestRunnerServer.cs | 24 ++++ .../IRemoteMessageSessionProxy.cs | 7 -- .../OutOfProcessEndpointRunner.cs | 109 ++++++++++-------- src/protos/RemoteEndpoint.proto | 11 +- src/protos/TestRunner.proto | 21 ++++ 22 files changed, 359 insertions(+), 148 deletions(-) create mode 100644 src/MyMessages/Messages/ALonleyMessage.cs create mode 100644 src/MyService/ALonleyMessageHandler.cs create mode 100644 src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/IRemoteMessageSessionProxy.cs delete mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/IntegrationScenarioContextClient.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerClient.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerImpl.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerServer.cs delete mode 100644 src/NServiceBus.IntegrationTesting/IRemoteMessageSessionProxy.cs create mode 100644 src/protos/TestRunner.proto diff --git a/src/MyMessages/Messages/ALonleyMessage.cs b/src/MyMessages/Messages/ALonleyMessage.cs new file mode 100644 index 00000000..ac319259 --- /dev/null +++ b/src/MyMessages/Messages/ALonleyMessage.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MyMessages.Messages +{ + public class ALonleyMessage + { + public Guid AnIdentifier { get; set; } + } +} diff --git a/src/MyService/ALonleyMessageHandler.cs b/src/MyService/ALonleyMessageHandler.cs new file mode 100644 index 00000000..44e071dc --- /dev/null +++ b/src/MyService/ALonleyMessageHandler.cs @@ -0,0 +1,14 @@ +using MyMessages.Messages; +using NServiceBus; +using System.Threading.Tasks; + +namespace MyService +{ + class ALonleyMessageHandler : IHandleMessages + { + public Task Handle(ALonleyMessage message, IMessageHandlerContext context) + { + return Task.CompletedTask; + } + } +} diff --git a/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs b/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs new file mode 100644 index 00000000..a72b6339 --- /dev/null +++ b/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs @@ -0,0 +1,46 @@ +using MyMessages.Messages; +using NServiceBus.AcceptanceTesting; +using NServiceBus.IntegrationTesting; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace MySystem.AcceptanceTests +{ + public class When_sending_ALonleyMessage_out_of_process + { + [OneTimeSetUp] + public async Task Setup() + { + await DockerCompose.Up(); + } + + [OneTimeTearDown] + public void Teardown() + { + DockerCompose.Down(); + } + + [Test] + public async Task The_message_is_sent_as_expected() + { + var theExpectedIdentifier = Guid.NewGuid(); + var context = await Scenario.Define() + .WithOutOfProcessEndpoint("MyService", EndpointReference.FromSolutionProject("MyService.TestProxy"), behavior => + { + behavior.When(remoteSession => remoteSession.Send("MyService", new ALonleyMessage() { AnIdentifier = theExpectedIdentifier })); + }) + .Done(c => + { + return c.OutgoingMessageOperations.Any(op => op.MessageType == typeof(ALonleyMessage)) || c.HasFailedMessages(); + }) + .Run(); + + //Assert.True(context.MessageWasProcessed()); + + Assert.False(context.HasFailedMessages()); + Assert.False(context.HasHandlingErrors()); + } + } +} \ No newline at end of file diff --git a/src/MySystem.AcceptanceTests/When_sending_AMessage.cs b/src/MySystem.AcceptanceTests/When_sending_AMessage.cs index 24e62cd3..82299625 100644 --- a/src/MySystem.AcceptanceTests/When_sending_AMessage.cs +++ b/src/MySystem.AcceptanceTests/When_sending_AMessage.cs @@ -1,5 +1,6 @@ #if NET48_OR_GREATER +using MyMessages.Messages; using NServiceBus.AcceptanceTesting; using NServiceBus.IntegrationTesting; using NUnit.Framework; @@ -40,7 +41,7 @@ public async Task AReplyMessage_is_received_and_ASaga_is_started() ctx.RemoteWhenExecuted = true; return Task.CompletedTask; }); - //behavior.When(session => session.Send(new AMessage() { AnIdentifier = theExpectedIdentifier })); + //behavior.When(remoteSession => remoteSession.Send(new AMessage() { AnIdentifier = theExpectedIdentifier })); }) .WithEndpoint(behavior => { diff --git a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs index 029244a0..f0814548 100644 --- a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs +++ b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs @@ -40,9 +40,9 @@ public async Task ASaga_is_completed() ctx.WhenExecuted = true; return Task.CompletedTask; }); - //behavior.When(session => + //behavior.When(remoteSession => //{ - // return session.Send("MyService", new StartASaga() {AnIdentifier = theExpectedIdentifier}); + // return remoteSession.Send("MyService", new StartASaga() { AnIdentifier = theExpectedIdentifier }); //}); //behavior.When(condition: ctx => //{ diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs index 47825e1a..c2a73221 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs @@ -10,15 +10,16 @@ public void Customize(EndpointConfiguration builder) var endpointName = CommandLine.GetEndpointName(); - var remoteEndpointServer = new RemoteEndpointServerV8(endpointName); + var remoteEndpointServer = new RemoteEndpointServerV8(); + remoteEndpointServer.Start(); - builder.RegisterComponents(services => services.AddSingleton(remoteEndpointServer)); + var testRunnerClient = new TestRunnerClient(endpointName); + builder.RegisterComponents(services => services.AddSingleton(remoteEndpointServer)); + builder.RegisterComponents(services => services.AddSingleton(testRunnerClient)); builder.EnableFeature(); - var integrationScenarioContextClient = new IntegrationScenarioContextClient(); - - builder.Pipeline.Register(new InterceptSendOperations(endpointName, integrationScenarioContextClient), "Intercept send operations reporting them to the remote test engine."); + builder.Pipeline.Register(new InterceptSendOperations(endpointName, testRunnerClient), "Intercept send operations streaming them to the remote test engine."); //builder.Pipeline.Register(new InterceptPublishOperations(endpointName, integrationScenarioContextClient), "Intercept publish operations reporting them to the remote test engine."); //builder.Pipeline.Register(new InterceptReplyOperations(endpointName, integrationScenarioContextClient), "Intercept reply operations reporting them to the remote test engine."); //builder.Pipeline.Register(new InterceptInvokedHandlers(endpointName, integrationScenarioContextClient), "Intercept invoked Message Handlers and Sagas reporting them to the remote test engine."); diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs index 4d5f804f..26125ab7 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs @@ -12,26 +12,27 @@ protected override void Setup(FeatureConfigurationContext context) context.RegisterStartupTask(container => { var server = container.GetService(); - return new CallbackStartupTask(server); + var client = container.GetService(); + return new CallbackStartupTask(server, client); }); } } class CallbackStartupTask : FeatureStartupTask { - readonly RemoteEndpointServerV8 _server; + readonly RemoteEndpointServerV8 server; + readonly TestRunnerClient client; - public CallbackStartupTask(RemoteEndpointServerV8 server) + public CallbackStartupTask(RemoteEndpointServerV8 server, TestRunnerClient client) { - _server = server; + this.server = server; + this.client = client; } protected override Task OnStart(IMessageSession session, CancellationToken cancellationToken = default) { - _server.RegisterMessageSession(session); - _server.NotifyEndpointStarted(); - - return Task.CompletedTask; + server.RegisterMessageSession(session); + return client.OnEndpointStarted(); } protected override Task OnStop(IMessageSession session, CancellationToken cancellationToken = default) diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs index e155b98b..9bac9432 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs @@ -7,12 +7,12 @@ namespace NServiceBus.IntegrationTesting.OutOfProcess class InterceptSendOperations : Behavior { readonly string endpointName; - readonly IntegrationScenarioContextClient integrationContext; + readonly TestRunnerClient testRunnerClient; - public InterceptSendOperations(string endpointName, IntegrationScenarioContextClient integrationContext) + public InterceptSendOperations(string endpointName, TestRunnerClient testRunnerClient) { this.endpointName = endpointName; - this.integrationContext = integrationContext; + this.testRunnerClient = testRunnerClient; } public override async Task Invoke(IOutgoingSendContext context, Func next) @@ -37,7 +37,8 @@ public override async Task Invoke(IOutgoingSendContext context, Func next) outgoingOperation.MessageInstance = context.Message.Instance; outgoingOperation.MessageHeaders = context.Headers; - integrationContext.AddOutogingOperation(outgoingOperation); + await testRunnerClient.RecordOutgoingMessageOperation(outgoingOperation); + try { await next(); diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs index 267a1e0d..2da13dd7 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs @@ -1,22 +1,36 @@ using System; -using System.Collections.Generic; -using System.Text; +using System.Text.Json; +using System.Threading.Tasks; namespace NServiceBus.IntegrationTesting.OutOfProcess.Nsb8 { internal class RemoteEndpointServerV8 : RemoteEndpointServer { - private IMessageSession _messageSession; + private IMessageSession messageSession; - public RemoteEndpointServerV8(string endpointName) - : base(endpointName) + public RemoteEndpointServerV8() { + OnSendRequest= r => + { + var messageType = Type.GetType(r.JsonMessageType); + var message = JsonSerializer.Deserialize(r.JsonMessage, messageType); + if (string.IsNullOrWhiteSpace(r.Destination)) + { + return messageSession.Send(message); + } + else + { + return messageSession.Send(r.Destination, message); + } + }; } + protected override Func OnSendRequest { get; } + public void RegisterMessageSession(IMessageSession messageSession) { - _messageSession = messageSession; + this.messageSession = messageSession; } } } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/IRemoteMessageSessionProxy.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/IRemoteMessageSessionProxy.cs new file mode 100644 index 00000000..245b6790 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/IRemoteMessageSessionProxy.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting +{ + public interface IRemoteMessageSessionProxy + { + Task Send(string destination, TMessage message); + Task Send(TMessage message); + } +} \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/IntegrationScenarioContextClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/IntegrationScenarioContextClient.cs deleted file mode 100644 index 8ee7a204..00000000 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/IntegrationScenarioContextClient.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace NServiceBus.IntegrationTesting.OutOfProcess -{ - public class IntegrationScenarioContextClient - { - public void AddOutogingOperation(OutgoingMessageOperation outgoingOperation) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj b/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj index ba56ca43..0f0e3e4e 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj @@ -1,21 +1,26 @@  - - netstandard2.0 - + + netcoreapp3.1;net48 + - - - - - + + + + + + + + + - - - + + + + diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs index 4a0dd921..b2bc162e 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs @@ -1,10 +1,10 @@ using Grpc.Core; -using System; +using System.Text.Json; using System.Threading.Tasks; namespace NServiceBus.IntegrationTesting.OutOfProcess { - public class RemoteEndpointClient + public class RemoteEndpointClient : IRemoteMessageSessionProxy { private Channel channel; private RemoteEndpoint.RemoteEndpointClient client; @@ -24,18 +24,21 @@ public async Task Stop() await channel.ShutdownAsync(); } - public async Task OnEndpointStarted(Func onStarted) + public async Task Send(string destination, TMessage message) { - var response = client.EndpointStarted(new Google.Protobuf.WellKnownTypes.Empty()); - while (await response.ResponseStream.MoveNext()) + var sendRequest = new SendRequest() { - var current = response.ResponseStream.Current; - await onStarted(current); + Destination = destination, + JsonMessage = JsonSerializer.Serialize(message), + JsonMessageType = message.GetType().AssemblyQualifiedName + }; - //EndpointStarted is fired only once. Don't need to stay - //connected to this stream any longer - return; - } + await client.SendAsync(sendRequest); + } + + public Task Send(TMessage message) + { + return Send(null, message); } } } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs index 8660d3df..93eea615 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs @@ -8,12 +8,11 @@ namespace NServiceBus.IntegrationTesting.OutOfProcess { class RemoteEndpointImpl : RemoteEndpointBase { - bool endpointStarted; - readonly string endpointName; + readonly Func onSendRequest; - public RemoteEndpointImpl(string endpointName) + public RemoteEndpointImpl(Func onSendRequest) { - this.endpointName = endpointName; + this.onSendRequest = onSendRequest; } public override Task Stop(Empty request, ServerCallContext context) @@ -23,22 +22,11 @@ public override Task Stop(Empty request, ServerCallContext context) return Task.FromResult(new Empty()); } - public override async Task EndpointStarted(Empty request, IServerStreamWriter responseStream, ServerCallContext context) + public override async Task Send(SendRequest request, ServerCallContext context) { - while (!endpointStarted) - { - await Task.Delay(500); - } + await onSendRequest(request); - await responseStream.WriteAsync(new EndpointStartedEvent() - { - EndpointName = endpointName - }); - } - - internal void NotifyEndpointStarted() - { - endpointStarted = true; + return new Empty(); } } } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs index 73b663a6..46324dbc 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs @@ -1,31 +1,24 @@ using Grpc.Core; +using System; +using System.Threading.Tasks; namespace NServiceBus.IntegrationTesting.OutOfProcess { public abstract class RemoteEndpointServer { - private readonly RemoteEndpointImpl remoteEndpoint; - readonly Server server; + Server server; - protected RemoteEndpointServer(string endpointName) + public void Start() { - remoteEndpoint = new RemoteEndpointImpl(endpointName); - EndpointName = endpointName; - server = new Server { - Services = { RemoteEndpoint.BindService(remoteEndpoint) }, + Services = { RemoteEndpoint.BindService(new RemoteEndpointImpl(OnSendRequest)) }, //TODO: replace with configurable port Ports = { new ServerPort("localhost", 30051, ServerCredentials.Insecure) } }; server.Start(); } - public string EndpointName { get; } - - public void NotifyEndpointStarted() - { - remoteEndpoint.NotifyEndpointStarted(); - } + protected abstract Func OnSendRequest { get; } } } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerClient.cs new file mode 100644 index 00000000..6ef6e1c7 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerClient.cs @@ -0,0 +1,39 @@ +using Grpc.Core; +using System.Text.Json; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting.OutOfProcess +{ + public class TestRunnerClient + { + private Channel channel; + private TestRunner.TestRunnerClient client; + + public TestRunnerClient(string endpointName) + { + //TODO: replace with configurable port + channel = new Channel("127.0.0.1:30052", ChannelCredentials.Insecure); + client = new TestRunner.TestRunnerClient(channel); + EndpointName = endpointName; + } + + public string EndpointName { get; } + + public async Task OnEndpointStarted() + { + _ = await client.OnEndpointStartedAsync(new EndpointStartedEvent() + { + EndpointName = EndpointName + }); + } + + public async Task RecordOutgoingMessageOperation(OutgoingMessageOperation outgoingMessageOperation) + { + _ = await client.RecordOutgoingMessageOperationAsync(new OutgoingMessageOperationEvent() + { + JsonOperation = JsonSerializer.Serialize(outgoingMessageOperation), + JsonOperationType = outgoingMessageOperation.GetType().AssemblyQualifiedName + }); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerImpl.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerImpl.cs new file mode 100644 index 00000000..acfaea98 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerImpl.cs @@ -0,0 +1,38 @@ +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using System; +using System.Text.Json; +using System.Threading.Tasks; +using static NServiceBus.IntegrationTesting.OutOfProcess.TestRunner; + +namespace NServiceBus.IntegrationTesting.OutOfProcess +{ + class TestRunnerImpl : TestRunnerBase + { + readonly Action onEndpointStarted; + readonly Action onOutgoingMessageOperation; + + public TestRunnerImpl(Action onEndpointStarted, Action onOutgoingMessageOperation) + { + this.onEndpointStarted = onEndpointStarted; + this.onOutgoingMessageOperation = onOutgoingMessageOperation; + } + + public override Task OnEndpointStarted(EndpointStartedEvent request, ServerCallContext context) + { + onEndpointStarted(request); + + return Task.FromResult(new Empty()); + } + + public override Task RecordOutgoingMessageOperation(OutgoingMessageOperationEvent request, ServerCallContext context) + { + var operationType = System.Type.GetType(request.JsonOperationType); + var operation = (OutgoingMessageOperation)JsonSerializer.Deserialize(request.JsonOperation, operationType); + + onOutgoingMessageOperation(operation); + + return Task.FromResult(new Empty()); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerServer.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerServer.cs new file mode 100644 index 00000000..34a83f73 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerServer.cs @@ -0,0 +1,24 @@ +using Grpc.Core; +using System; + +namespace NServiceBus.IntegrationTesting.OutOfProcess +{ + public class TestRunnerServer + { + private readonly TestRunnerImpl testRunner; + readonly Server server; + + public TestRunnerServer(Action onEndpointStarted, Action onOutgoingMessageOperation) + { + testRunner = new TestRunnerImpl(onEndpointStarted, onOutgoingMessageOperation); + + server = new Server + { + Services = { TestRunner.BindService(testRunner) }, + //TODO: replace with configurable port + Ports = { new ServerPort("localhost", 30052, ServerCredentials.Insecure) } + }; + server.Start(); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting/IRemoteMessageSessionProxy.cs b/src/NServiceBus.IntegrationTesting/IRemoteMessageSessionProxy.cs deleted file mode 100644 index 5d7fb6e0..00000000 --- a/src/NServiceBus.IntegrationTesting/IRemoteMessageSessionProxy.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NServiceBus.IntegrationTesting -{ - public interface IRemoteMessageSessionProxy - { - - } -} \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs index b1341f80..00344bfd 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs @@ -15,6 +15,7 @@ namespace NServiceBus.IntegrationTesting class OutOfProcessEndpointRunner : ComponentRunner { RemoteEndpointClient remoteEndpoint; + private TestRunnerServer testRunnerServer; static ILog Logger = LogManager.GetLogger(); private Process process; private Task outputTask; @@ -26,8 +27,21 @@ class OutOfProcessEndpointRunner : ComponentRunner public OutOfProcessEndpointRunner(RunDescriptor runDescriptor, string endpointName, Process process, IList remoteWhens) { remoteEndpoint = new RemoteEndpointClient(); - Name = endpointName; + testRunnerServer = new TestRunnerServer + ( + onEndpointStarted: e => + { + Logger.Info($"Received EndpointStarted event from remote endpoint '{e.EndpointName}'."); + remoteEndpointStarted = true; + }, + onOutgoingMessageOperation: operation => + { + ((IntegrationScenarioContext)runDescriptor.ScenarioContext).AddOutogingOperation(operation); + } + ); + this.runDescriptor = runDescriptor; + Name = endpointName; this.process = process; this.remoteWhens = remoteWhens; } @@ -41,7 +55,7 @@ public override async Task Start(CancellationToken token) outputTask = process.StandardOutput.ReadToEndAsync(); errorTask = process.StandardError.ReadToEndAsync(); - await ConnectToRemoteEndpointWithRetries(); + //await ConnectToRemoteEndpointWithRetries(); while (!remoteEndpointStarted) { @@ -54,9 +68,6 @@ public override async Task Start(CancellationToken token) //TODO: How to access ScenarioContext.CurrentEndpoint // ScenarioContext.CurrentEndpoint = Name; - //build the remote session proxy - IRemoteMessageSessionProxy messageSessionProxy = null; - try { if (remoteWhens.Count != 0) @@ -89,7 +100,7 @@ await Task.Run(async () => continue; } - if (await when.ExecuteAction((IntegrationScenarioContext)runDescriptor.ScenarioContext, messageSessionProxy).ConfigureAwait(false)) + if (await when.ExecuteAction((IntegrationScenarioContext)runDescriptor.ScenarioContext, remoteEndpoint).ConfigureAwait(false)) { executedWhens.Add(when.Id); } @@ -108,46 +119,54 @@ await Task.Run(async () => } } - private async Task ConnectToRemoteEndpointWithRetries() - { - var connected = false; + //private async Task ConnectToRemoteEndpointWithRetries() + //{ + // var connected = false; - //NServiceBus ATT comes with a default hardcoded 2 minutes endpoints startup timeout - var maxAttempts = 240; - var msDelayBetweenAttempts = 500; - var attempts = 0; - - while (!connected) - { - try - { - Logger.Debug($"Connecting to remote endpoint: '{Name}' - Attempt {attempts + 1}"); - await remoteEndpoint.OnEndpointStarted(e => - { - Logger.Info($"Received EndpointStarted event from remote endpoint '{e.EndpointName}'."); - - remoteEndpointStarted = true; - return Task.CompletedTask; - }); - - connected = true; - } - catch (Exception ex) when ((ex is RpcException rpcEx) && rpcEx.StatusCode == StatusCode.Unavailable) - { - attempts++; - if (attempts > maxAttempts) - { - Logger.Error($"Failed to connect to remote endpoint '{Name}' after {attempts + 1} attempts.", rpcEx); - throw; - } - - Logger.Debug($"Failed to connect to remote endpoint '{Name}', waiting to retry."); - await Task.Delay(msDelayBetweenAttempts); - } - } - - Logger.Debug($"Connected to remote endpoint: '{Name}'."); - } + // //NServiceBus ATT comes with a default hardcoded 2 minutes endpoints startup timeout + // var maxAttempts = 240; + // var msDelayBetweenAttempts = 500; + // var attempts = 0; + + // while (!connected) + // { + // try + // { + // Logger.Debug($"Connecting to remote endpoint: '{Name}' - Attempt {attempts + 1}"); + // await remoteEndpoint.OnEndpointStarted(e => + // { + + + // remoteEndpointStarted = true; + // return Task.CompletedTask; + // }); + + // connected = true; + // } + // catch (Exception ex) when ((ex is RpcException rpcEx) && rpcEx.StatusCode == StatusCode.Unavailable) + // { + // attempts++; + // if (attempts > maxAttempts) + // { + // Logger.Error($"Failed to connect to remote endpoint '{Name}' after {attempts + 1} attempts.", rpcEx); + // throw; + // } + + // Logger.Debug($"Failed to connect to remote endpoint '{Name}', waiting to retry."); + // await Task.Delay(msDelayBetweenAttempts); + // } + // } + + // Logger.Debug($"Connected to remote endpoint: '{Name}'."); + // Logger.Debug($"Attaching remote endpoint '{Name}' OutgoingMessageOperation stream."); + // await remoteEndpoint.OnOutgoingMessageOperation(outgoingMessageOperation => + // { + // ((IntegrationScenarioContext)runDescriptor.ScenarioContext).AddOutogingOperation(outgoingMessageOperation); + + // return Task.CompletedTask; + // }); + // Logger.Debug($"Remote endpoint '{Name}' OutgoingMessageOperation stream attached."); + //} public override async Task Stop() { diff --git a/src/protos/RemoteEndpoint.proto b/src/protos/RemoteEndpoint.proto index e9af212e..11f357da 100644 --- a/src/protos/RemoteEndpoint.proto +++ b/src/protos/RemoteEndpoint.proto @@ -1,16 +1,17 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; -import "google/protobuf/any.proto"; option csharp_namespace = "NServiceBus.IntegrationTesting.OutOfProcess"; -package NServiceBus.IntegrationTesting; +package NServiceBus.IntegrationTesting.RemoteEndpoint; service RemoteEndpoint { rpc Stop (google.protobuf.Empty) returns (google.protobuf.Empty); - rpc EndpointStarted (google.protobuf.Empty) returns (stream EndpointStartedEvent); + rpc Send(SendRequest) returns (google.protobuf.Empty); } -message EndpointStartedEvent { - string endpointName = 1; +message SendRequest{ + string destination = 1; + string jsonMessage = 2; + string jsonMessageType = 3; } \ No newline at end of file diff --git a/src/protos/TestRunner.proto b/src/protos/TestRunner.proto new file mode 100644 index 00000000..60aad9c1 --- /dev/null +++ b/src/protos/TestRunner.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "google/protobuf/any.proto"; + +option csharp_namespace = "NServiceBus.IntegrationTesting.OutOfProcess"; +package NServiceBus.IntegrationTesting.TestRunner; + +service TestRunner { + rpc OnEndpointStarted (EndpointStartedEvent) returns (google.protobuf.Empty); + rpc RecordOutgoingMessageOperation (OutgoingMessageOperationEvent) returns (google.protobuf.Empty); +} + +message EndpointStartedEvent { + string endpointName = 1; +} + +message OutgoingMessageOperationEvent { + string jsonOperation = 1; + string jsonOperationType = 2; +} \ No newline at end of file From bc3a91b7e19aa7b3a0832ca52b188bc5175e9923 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 7 Jan 2022 22:19:32 +0100 Subject: [PATCH 35/52] Pilot remote endpoint to send messages and (partially) intercepts send operations --- src/MyService.TestProxy/Program.cs | 1 + .../When_sending_ALonleyMessage.cs | 15 +++- .../AutoConfiguration.cs | 5 +- .../DebuggerAttachedCallback.cs | 73 +++++++++++++++++++ .../EndpointStartupCallback.cs | 8 +- .../InterceptSendOperations.cs | 4 +- .../RemoteEndpointServerV8.cs | 20 +++-- .../CommandLine.cs | 24 ++++++ ...Bus.IntegrationTesting.OutOfProcess.csproj | 2 +- .../OutOfProcessEndpointRunnerClient.cs | 64 ++++++++++++++++ .../OutOfProcessEndpointRunnerImpl.cs | 67 +++++++++++++++++ .../OutOfProcessEndpointRunnerServer.cs | 37 ++++++++++ .../Properties.cs | 8 ++ .../RemoteEndpointClient.cs | 4 +- .../RemoteEndpointServer.cs | 8 +- .../TestRunnerClient.cs | 39 ---------- .../TestRunnerImpl.cs | 38 ---------- .../TestRunnerServer.cs | 24 ------ .../APIApprovals.Approve_API.approved.txt | 2 +- .../APIApprovals.Approve_API.approved.txt | 2 +- .../IntegrationScenarioContext.cs | 62 +++++++++++++++- .../OutOfProcessEndpointBehavior.cs | 11 ++- .../OutOfProcessEndpointRunner.cs | 28 +++++-- src/protos/OutOfProcessEndpointRunner.proto | 34 +++++++++ src/protos/TestRunner.proto | 21 ------ 25 files changed, 444 insertions(+), 157 deletions(-) create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/DebuggerAttachedCallback.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerClient.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerImpl.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerServer.cs create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/Properties.cs delete mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerClient.cs delete mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerImpl.cs delete mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerServer.cs create mode 100644 src/protos/OutOfProcessEndpointRunner.proto delete mode 100644 src/protos/TestRunner.proto diff --git a/src/MyService.TestProxy/Program.cs b/src/MyService.TestProxy/Program.cs index 32759ff0..fceb21bb 100644 --- a/src/MyService.TestProxy/Program.cs +++ b/src/MyService.TestProxy/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using Microsoft.Extensions.Hosting; using NServiceBus; using Serilog; diff --git a/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs b/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs index a72b6339..eff8d21f 100644 --- a/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs +++ b/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs @@ -1,6 +1,7 @@ using MyMessages.Messages; using NServiceBus.AcceptanceTesting; using NServiceBus.IntegrationTesting; +using NServiceBus.IntegrationTesting.OutOfProcess; using NUnit.Framework; using System; using System.Linq; @@ -25,11 +26,21 @@ public void Teardown() [Test] public async Task The_message_is_sent_as_expected() { + const string EndpointName = "MyService"; + var theExpectedIdentifier = Guid.NewGuid(); var context = await Scenario.Define() - .WithOutOfProcessEndpoint("MyService", EndpointReference.FromSolutionProject("MyService.TestProxy"), behavior => + .WithOutOfProcessEndpoint(EndpointName, EndpointReference.FromSolutionProject("MyService.TestProxy"), behavior => { - behavior.When(remoteSession => remoteSession.Send("MyService", new ALonleyMessage() { AnIdentifier = theExpectedIdentifier })); + behavior.When( + //condition: ctx => + //{ + // return ctx.GetProperties(EndpointName).ContainsKey(Properties.DebuggerAttached); + //}, + action: remoteSession => + { + return remoteSession.Send(EndpointName, new ALonleyMessage() { AnIdentifier = theExpectedIdentifier }); + }); }) .Done(c => { diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs index c2a73221..d3acafb9 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs @@ -10,14 +10,15 @@ public void Customize(EndpointConfiguration builder) var endpointName = CommandLine.GetEndpointName(); - var remoteEndpointServer = new RemoteEndpointServerV8(); + var remoteEndpointServer = new RemoteEndpointServerV8(CommandLine.GetEndpointPort()); remoteEndpointServer.Start(); - var testRunnerClient = new TestRunnerClient(endpointName); + var testRunnerClient = new OutOfProcessEndpointRunnerClient(endpointName, CommandLine.GetRunnerPort()); builder.RegisterComponents(services => services.AddSingleton(remoteEndpointServer)); builder.RegisterComponents(services => services.AddSingleton(testRunnerClient)); builder.EnableFeature(); + builder.EnableFeature(); builder.Pipeline.Register(new InterceptSendOperations(endpointName, testRunnerClient), "Intercept send operations streaming them to the remote test engine."); //builder.Pipeline.Register(new InterceptPublishOperations(endpointName, integrationScenarioContextClient), "Intercept publish operations reporting them to the remote test engine."); diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/DebuggerAttachedCallback.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/DebuggerAttachedCallback.cs new file mode 100644 index 00000000..4cddf997 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/DebuggerAttachedCallback.cs @@ -0,0 +1,73 @@ +using Microsoft.Extensions.DependencyInjection; +using NServiceBus.Features; +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting.OutOfProcess.Nsb8 +{ + class DebuggerAttachedCallback : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(container => + { + var client = container.GetService(); + return new DebuggerAtachedStartupTask(client); + }); + } + } + + class DebuggerAtachedStartupTask : FeatureStartupTask + { + private OutOfProcessEndpointRunnerClient client; + private CancellationTokenSource cancellationTokenSource; + private Task checkDebuggerTask; + + public DebuggerAtachedStartupTask(OutOfProcessEndpointRunnerClient client) + { + this.client = client; + } + + protected override Task OnStart(IMessageSession session, CancellationToken cancellationToken = default) + { + cancellationTokenSource = new CancellationTokenSource(); + + checkDebuggerTask = Task.Run(() => CheckDebuggerStatus(cancellationTokenSource.Token), CancellationToken.None); + + return Task.CompletedTask; + } + + protected override async Task OnStop(IMessageSession session, CancellationToken cancellationToken = default) + { + cancellationTokenSource.Cancel(); + + if (checkDebuggerTask != null) + { + await checkDebuggerTask; + } + } + + async Task CheckDebuggerStatus(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + if (Debugger.IsAttached) + { + await client.SetContextProperty(Properties.DebuggerAttached, bool.TrueString); + break; + } + + await Task.Delay(100); + } + catch (Exception ex) when (ex is OperationCanceledException && cancellationToken.IsCancellationRequested) + { + break; + } + } + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs index 26125ab7..a3f82902 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/EndpointStartupCallback.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using NServiceBus.Features; +using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -12,7 +14,7 @@ protected override void Setup(FeatureConfigurationContext context) context.RegisterStartupTask(container => { var server = container.GetService(); - var client = container.GetService(); + var client = container.GetService(); return new CallbackStartupTask(server, client); }); } @@ -21,9 +23,9 @@ protected override void Setup(FeatureConfigurationContext context) class CallbackStartupTask : FeatureStartupTask { readonly RemoteEndpointServerV8 server; - readonly TestRunnerClient client; + readonly OutOfProcessEndpointRunnerClient client; - public CallbackStartupTask(RemoteEndpointServerV8 server, TestRunnerClient client) + public CallbackStartupTask(RemoteEndpointServerV8 server, OutOfProcessEndpointRunnerClient client) { this.server = server; this.client = client; diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs index 9bac9432..b1f6da21 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs @@ -7,9 +7,9 @@ namespace NServiceBus.IntegrationTesting.OutOfProcess class InterceptSendOperations : Behavior { readonly string endpointName; - readonly TestRunnerClient testRunnerClient; + readonly OutOfProcessEndpointRunnerClient testRunnerClient; - public InterceptSendOperations(string endpointName, TestRunnerClient testRunnerClient) + public InterceptSendOperations(string endpointName, OutOfProcessEndpointRunnerClient testRunnerClient) { this.endpointName = endpointName; this.testRunnerClient = testRunnerClient; diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs index 2da13dd7..ed700703 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs @@ -8,20 +8,28 @@ internal class RemoteEndpointServerV8 : RemoteEndpointServer { private IMessageSession messageSession; - public RemoteEndpointServerV8() + public RemoteEndpointServerV8(int port) + : base(port) { - OnSendRequest= r => + OnSendRequest= async r => { var messageType = Type.GetType(r.JsonMessageType); var message = JsonSerializer.Deserialize(r.JsonMessage, messageType); - if (string.IsNullOrWhiteSpace(r.Destination)) + try { - return messageSession.Send(message); + if (string.IsNullOrWhiteSpace(r.Destination)) + { + await messageSession.Send(message).ConfigureAwait(false); + } + else + { + await messageSession.Send(r.Destination, message).ConfigureAwait(false); + } } - else + catch (Exception ex) { - return messageSession.Send(r.Destination, message); + throw; } }; } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/CommandLine.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/CommandLine.cs index 89db029d..03e95291 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/CommandLine.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/CommandLine.cs @@ -31,5 +31,29 @@ public static string GetEndpointName() return endpointName; } + + public static int GetRunnerPort() + { + var runnerPort = Environment.CommandLine + .Split(' ') + .Where(x => x.StartsWith("--runnerPort=")) + .Single() + .Split('=') + .Last(); + + return int.Parse(runnerPort); + } + + public static int GetEndpointPort() + { + var endpointPort = Environment.CommandLine + .Split(' ') + .Where(x => x.StartsWith("--endpointPort=")) + .Single() + .Split('=') + .Last(); + + return int.Parse(endpointPort); + } } } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj b/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj index 0f0e3e4e..bdc2a855 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerClient.cs new file mode 100644 index 00000000..498a8906 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerClient.cs @@ -0,0 +1,64 @@ +using Grpc.Core; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting.OutOfProcess +{ + public class OutOfProcessEndpointRunnerClient + { + Channel channel; + OutOfProcessEndpointRunner.OutOfProcessEndpointRunnerClient client; + + public OutOfProcessEndpointRunnerClient(string endpointName, int serverPort) + { + channel = new Channel($"127.0.0.1:{serverPort}", ChannelCredentials.Insecure); + client = new OutOfProcessEndpointRunner.OutOfProcessEndpointRunnerClient(channel); + EndpointName = endpointName; + } + + public string EndpointName { get; } + + public async Task OnEndpointStarted() + { + _ = await client.OnEndpointStartedAsync(new EndpointStartedEvent() + { + EndpointName = EndpointName + }); + } + + public async Task RecordOutgoingMessageOperation(OutgoingMessageOperation outgoingMessageOperation) + { + var request = new OutgoingMessageOperationEvent() + { + OperationTypeAssemblyQualifiedName = outgoingMessageOperation.GetType().AssemblyQualifiedName, + SenderEndpoint = outgoingMessageOperation.SenderEndpoint, + //MessageInstanceJson = outgoingMessageOperation.MessageInstance != null + // ? JsonSerializer.Serialize(outgoingMessageOperation.MessageInstance) + // : null, + MessageId = outgoingMessageOperation.MessageId, + //MessageHeadersJson = outgoingMessageOperation.MessageHeaders != null + // ? JsonSerializer.Serialize(outgoingMessageOperation.MessageHeaders) + // : null, + MessageTypeAssemblyQualifiedName = outgoingMessageOperation.MessageType != null + ? outgoingMessageOperation.MessageType.AssemblyQualifiedName + : null, + //OperationErrorTypeAssemblyQualifiedName = outgoingMessageOperation.OperationError != null + // ? outgoingMessageOperation.OperationError.GetType().AssemblyQualifiedName + // : null, + //OperationErrorJson = outgoingMessageOperation.OperationError != null + // ? JsonSerializer.Serialize(outgoingMessageOperation.OperationError) + // : null + }; + _ = await client.RecordOutgoingMessageOperationAsync(request); + } + + public async Task SetContextProperty(string propertyName, string propertyValue) + { + _ = await client.SetContextPropertyAsync(new SetContextPropertyRequest() + { + EndpointName = EndpointName, + PropertyName = propertyName, + PropertyValue = propertyValue + }); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerImpl.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerImpl.cs new file mode 100644 index 00000000..3919a6b5 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerImpl.cs @@ -0,0 +1,67 @@ +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Threading.Tasks; +using static NServiceBus.IntegrationTesting.OutOfProcess.OutOfProcessEndpointRunner; + +namespace NServiceBus.IntegrationTesting.OutOfProcess +{ + class OutOfProcessEndpointRunnerImpl : OutOfProcessEndpointRunnerBase + { + readonly Action onEndpointStarted; + readonly Action onOutgoingMessageOperation; + private readonly Action<(string EndpointName, string PropertyName, string PropertyValue)> onSetContextProperty; + + public OutOfProcessEndpointRunnerImpl( + Action onEndpointStarted, + Action onOutgoingMessageOperation, + Action<(string EndpointName, string PropertyName, string PropertyValue)> onSetContextProperty + ) + { + this.onEndpointStarted = onEndpointStarted; + this.onOutgoingMessageOperation = onOutgoingMessageOperation; + this.onSetContextProperty = onSetContextProperty; + } + + public override Task OnEndpointStarted(EndpointStartedEvent request, ServerCallContext context) + { + onEndpointStarted(request); + + return Task.FromResult(new Empty()); + } + + public override Task RecordOutgoingMessageOperation(OutgoingMessageOperationEvent request, ServerCallContext context) + { + var operationType = System.Type.GetType(request.OperationTypeAssemblyQualifiedName); + var operation = (OutgoingMessageOperation)Activator.CreateInstance(operationType); + + operation.SenderEndpoint = request.SenderEndpoint; + operation.MessageId = request.MessageId; + operation.MessageType = !string.IsNullOrWhiteSpace(request.MessageTypeAssemblyQualifiedName) + ? System.Type.GetType(request.MessageTypeAssemblyQualifiedName) + : null; + operation.MessageInstance = !string.IsNullOrWhiteSpace(request.MessageInstanceJson) + ? JsonSerializer.Deserialize(request.MessageInstanceJson, operation.MessageType) + : null; + operation.MessageHeaders = !string.IsNullOrWhiteSpace(request.MessageHeadersJson) + ? (Dictionary)JsonSerializer.Deserialize(request.MessageHeadersJson, typeof(Dictionary)) + : null; + operation.OperationError = !string.IsNullOrWhiteSpace(request.OperationErrorJson) + ? (Exception)JsonSerializer.Deserialize(request.OperationErrorJson, System.Type.GetType(request.OperationErrorTypeAssemblyQualifiedName)) + : null; + + onOutgoingMessageOperation(operation); + + return Task.FromResult(new Empty()); + } + + public override Task SetContextProperty(SetContextPropertyRequest request, ServerCallContext context) + { + onSetContextProperty((request.EndpointName, request.PropertyName, request.PropertyValue)); + + return Task.FromResult(new Empty()); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerServer.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerServer.cs new file mode 100644 index 00000000..2e212843 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerServer.cs @@ -0,0 +1,37 @@ +using Grpc.Core; +using System; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting.OutOfProcess +{ + public class OutOfProcessEndpointRunnerServer + { + private readonly OutOfProcessEndpointRunnerImpl testRunner; + readonly Server server; + + public OutOfProcessEndpointRunnerServer( + int port, + Action onEndpointStarted, + Action onOutgoingMessageOperation, + Action<(string EndpointName, string PropertyName, string PropertyValue)> onSetContextProperty) + { + testRunner = new OutOfProcessEndpointRunnerImpl( + onEndpointStarted, + onOutgoingMessageOperation, + onSetContextProperty); + + server = new Server + { + Services = { OutOfProcessEndpointRunner.BindService(testRunner) }, + //TODO: replace with configurable port + Ports = { new ServerPort("localhost", port, ServerCredentials.Insecure) } + }; + server.Start(); + } + + public Task Stop() + { + return server.ShutdownAsync(); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/Properties.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/Properties.cs new file mode 100644 index 00000000..d23da643 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/Properties.cs @@ -0,0 +1,8 @@ +namespace NServiceBus.IntegrationTesting.OutOfProcess +{ + public static class Properties + { + const string Prefix = "NServiceBus.IntegrationTesting"; + public const string DebuggerAttached = $"{Prefix}.{nameof(DebuggerAttached)}"; + } +} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs index b2bc162e..2985b7a8 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs @@ -9,10 +9,10 @@ public class RemoteEndpointClient : IRemoteMessageSessionProxy private Channel channel; private RemoteEndpoint.RemoteEndpointClient client; - public RemoteEndpointClient() + public RemoteEndpointClient(int port) { //TODO: replace with configurable port - channel = new Channel("127.0.0.1:30051", ChannelCredentials.Insecure); + channel = new Channel($"127.0.0.1:{port}", ChannelCredentials.Insecure); client = new RemoteEndpoint.RemoteEndpointClient(channel); } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs index 46324dbc..1c5f9a22 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs @@ -7,6 +7,12 @@ namespace NServiceBus.IntegrationTesting.OutOfProcess public abstract class RemoteEndpointServer { Server server; + private readonly int port; + + protected RemoteEndpointServer(int port) + { + this.port = port; + } public void Start() { @@ -14,7 +20,7 @@ public void Start() { Services = { RemoteEndpoint.BindService(new RemoteEndpointImpl(OnSendRequest)) }, //TODO: replace with configurable port - Ports = { new ServerPort("localhost", 30051, ServerCredentials.Insecure) } + Ports = { new ServerPort("localhost", port, ServerCredentials.Insecure) } }; server.Start(); } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerClient.cs deleted file mode 100644 index 6ef6e1c7..00000000 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerClient.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Grpc.Core; -using System.Text.Json; -using System.Threading.Tasks; - -namespace NServiceBus.IntegrationTesting.OutOfProcess -{ - public class TestRunnerClient - { - private Channel channel; - private TestRunner.TestRunnerClient client; - - public TestRunnerClient(string endpointName) - { - //TODO: replace with configurable port - channel = new Channel("127.0.0.1:30052", ChannelCredentials.Insecure); - client = new TestRunner.TestRunnerClient(channel); - EndpointName = endpointName; - } - - public string EndpointName { get; } - - public async Task OnEndpointStarted() - { - _ = await client.OnEndpointStartedAsync(new EndpointStartedEvent() - { - EndpointName = EndpointName - }); - } - - public async Task RecordOutgoingMessageOperation(OutgoingMessageOperation outgoingMessageOperation) - { - _ = await client.RecordOutgoingMessageOperationAsync(new OutgoingMessageOperationEvent() - { - JsonOperation = JsonSerializer.Serialize(outgoingMessageOperation), - JsonOperationType = outgoingMessageOperation.GetType().AssemblyQualifiedName - }); - } - } -} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerImpl.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerImpl.cs deleted file mode 100644 index acfaea98..00000000 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerImpl.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Google.Protobuf.WellKnownTypes; -using Grpc.Core; -using System; -using System.Text.Json; -using System.Threading.Tasks; -using static NServiceBus.IntegrationTesting.OutOfProcess.TestRunner; - -namespace NServiceBus.IntegrationTesting.OutOfProcess -{ - class TestRunnerImpl : TestRunnerBase - { - readonly Action onEndpointStarted; - readonly Action onOutgoingMessageOperation; - - public TestRunnerImpl(Action onEndpointStarted, Action onOutgoingMessageOperation) - { - this.onEndpointStarted = onEndpointStarted; - this.onOutgoingMessageOperation = onOutgoingMessageOperation; - } - - public override Task OnEndpointStarted(EndpointStartedEvent request, ServerCallContext context) - { - onEndpointStarted(request); - - return Task.FromResult(new Empty()); - } - - public override Task RecordOutgoingMessageOperation(OutgoingMessageOperationEvent request, ServerCallContext context) - { - var operationType = System.Type.GetType(request.JsonOperationType); - var operation = (OutgoingMessageOperation)JsonSerializer.Deserialize(request.JsonOperation, operationType); - - onOutgoingMessageOperation(operation); - - return Task.FromResult(new Empty()); - } - } -} diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerServer.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerServer.cs deleted file mode 100644 index 34a83f73..00000000 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/TestRunnerServer.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Grpc.Core; -using System; - -namespace NServiceBus.IntegrationTesting.OutOfProcess -{ - public class TestRunnerServer - { - private readonly TestRunnerImpl testRunner; - readonly Server server; - - public TestRunnerServer(Action onEndpointStarted, Action onOutgoingMessageOperation) - { - testRunner = new TestRunnerImpl(onEndpointStarted, onOutgoingMessageOperation); - - server = new Server - { - Services = { TestRunner.BindService(testRunner) }, - //TODO: replace with configurable port - Ports = { new ServerPort("localhost", 30052, ServerCredentials.Insecure) } - }; - server.Start(); - } - } -} diff --git a/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt b/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt index 6a5d2874..4ed4d955 100644 --- a/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt +++ b/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt @@ -55,13 +55,13 @@ namespace NServiceBus.IntegrationTesting System.Guid Id { get; } System.Threading.Tasks.Task ExecuteAction(NServiceBus.IntegrationTesting.IntegrationScenarioContext context, NServiceBus.IntegrationTesting.IRemoteMessageSessionProxy session); } - public interface IRemoteMessageSessionProxy { } public class IntegrationScenarioContext : NServiceBus.AcceptanceTesting.ScenarioContext { public IntegrationScenarioContext() { } public System.Collections.Generic.IEnumerable InvokedHandlers { get; } public System.Collections.Generic.IEnumerable InvokedSagas { get; } public System.Collections.Generic.IEnumerable OutgoingMessageOperations { get; } + public System.Collections.Generic.IReadOnlyDictionary GetProperties(string endpointName) { } public bool HandlerWasInvoked() { } public bool HasFailedMessages() { } public bool HasHandlingErrors() { } diff --git a/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt b/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt index 6a5d2874..4ed4d955 100644 --- a/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt +++ b/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt @@ -55,13 +55,13 @@ namespace NServiceBus.IntegrationTesting System.Guid Id { get; } System.Threading.Tasks.Task ExecuteAction(NServiceBus.IntegrationTesting.IntegrationScenarioContext context, NServiceBus.IntegrationTesting.IRemoteMessageSessionProxy session); } - public interface IRemoteMessageSessionProxy { } public class IntegrationScenarioContext : NServiceBus.AcceptanceTesting.ScenarioContext { public IntegrationScenarioContext() { } public System.Collections.Generic.IEnumerable InvokedHandlers { get; } public System.Collections.Generic.IEnumerable InvokedSagas { get; } public System.Collections.Generic.IEnumerable OutgoingMessageOperations { get; } + public System.Collections.Generic.IReadOnlyDictionary GetProperties(string endpointName) { } public bool HandlerWasInvoked() { } public bool HasFailedMessages() { } public bool HasHandlingErrors() { } diff --git a/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs b/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs index 1b57dd8a..4d071c62 100644 --- a/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs +++ b/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs @@ -3,21 +3,61 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; namespace NServiceBus.IntegrationTesting { public class IntegrationScenarioContext : ScenarioContext { - readonly ConcurrentBag invokedHandlers = new ConcurrentBag(); - readonly ConcurrentBag invokedSagas = new ConcurrentBag(); - readonly ConcurrentBag outgoingMessageOperations = new ConcurrentBag(); - readonly Dictionary> timeoutRescheduleRules = new Dictionary>(); + static readonly object syncRoot = new object(); + readonly ConcurrentBag invokedHandlers = new(); + readonly ConcurrentBag invokedSagas = new(); + readonly ConcurrentBag outgoingMessageOperations = new(); + readonly Dictionary> timeoutRescheduleRules = new(); + readonly Dictionary> properties = new(); + readonly Dictionary ports = new(); public IEnumerable InvokedHandlers { get { return invokedHandlers; } } + + internal (int runnerPort, int endpointPort) GetCommunicationPorts(string endpointName) + { + if (ports.Count == 0) + { + var p = (runnerPort: 30050, endpointPort: 40050); + ports.Add(endpointName, p); + + return p; + } + + if (ports.TryGetValue(endpointName, out var port)) + { + return port; + } + + var last = ports.Last(); + var newPorts = (runnerPort: last.Value.runnerPort + 1, endpointPort: last.Value.endpointPort + 1); + ports.Add(endpointName, newPorts); + + return newPorts; + } + public IEnumerable InvokedSagas { get { return invokedSagas; } } public IEnumerable OutgoingMessageOperations { get { return outgoingMessageOperations; } } + public IReadOnlyDictionary GetProperties(string endpointName) + { + lock (syncRoot) + { + if (!properties.TryGetValue(endpointName, out var endpointProps)) + { + endpointProps = new Dictionary(); + } + + return endpointProps; + } + } + internal HandlerInvocation CaptureInvokedHandler(HandlerInvocation invocation) { invokedHandlers.Add(invocation); @@ -40,6 +80,20 @@ internal bool TryGetTimeoutRescheduleRule(Type timeoutMessageType, out Func(); + properties[endpointName] = endpointProps; + } + + endpointProps[propertyName] = propertyValue; + } + } + internal void AddOutogingOperation(OutgoingMessageOperation outgoingMessageOperation) { outgoingMessageOperations.Add(outgoingMessageOperation); diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs index d1168624..e83c33b1 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointBehavior.cs @@ -3,11 +3,14 @@ using System.Diagnostics; using System.Threading.Tasks; using NServiceBus.AcceptanceTesting.Support; +using NServiceBus.Logging; namespace NServiceBus.IntegrationTesting { class OutOfProcessEndpointBehavior : IComponentBehavior { + static ILog Logger = LogManager.GetLogger(); + readonly EndpointReference reference; readonly IList remoteWhens; readonly string endpointName; @@ -21,9 +24,11 @@ public OutOfProcessEndpointBehavior(string endpointName, EndpointReference refer public Task CreateRunner(RunDescriptor runDescriptor) { + (var runnerPort, var endpointPort) = ((IntegrationScenarioContext)runDescriptor.ScenarioContext).GetCommunicationPorts(endpointName); + var process = new Process(); process.StartInfo.FileName = @"dotnet"; - process.StartInfo.Arguments = $"run --project \"{reference.GetProjectFilePath()}\" --integrationTest --endpointName={endpointName}"; + process.StartInfo.Arguments = $"run --project \"{reference.GetProjectFilePath()}\" --integrationTest --endpointName={endpointName} --runnerPort={runnerPort} --endpointPort={endpointPort}"; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; @@ -31,7 +36,9 @@ public Task CreateRunner(RunDescriptor runDescriptor) process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; process.StartInfo.CreateNoWindow = true; - var runner = new OutOfProcessEndpointRunner(runDescriptor, endpointName, process, remoteWhens); + Logger.Debug($"Remote endpoint '{endpointName}' process arguments: {process.StartInfo.Arguments}"); + + var runner = new OutOfProcessEndpointRunner(runDescriptor, endpointName, process, remoteWhens, runnerPort, endpointPort); return Task.FromResult((ComponentRunner)runner); } } diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs index 00344bfd..6feff6c3 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs @@ -14,21 +14,23 @@ namespace NServiceBus.IntegrationTesting { class OutOfProcessEndpointRunner : ComponentRunner { + static ILog Logger = LogManager.GetLogger(); + RemoteEndpointClient remoteEndpoint; - private TestRunnerServer testRunnerServer; - static ILog Logger = LogManager.GetLogger(); - private Process process; - private Task outputTask; - private Task errorTask; + OutOfProcessEndpointRunnerServer outOfProcessEndpointRunnerServer; + Process process; + Task outputTask; + Task errorTask; readonly RunDescriptor runDescriptor; bool remoteEndpointStarted; readonly IList remoteWhens; - public OutOfProcessEndpointRunner(RunDescriptor runDescriptor, string endpointName, Process process, IList remoteWhens) + public OutOfProcessEndpointRunner(RunDescriptor runDescriptor, string endpointName, Process process, IList remoteWhens, int runnerPort, int endpointPort) { - remoteEndpoint = new RemoteEndpointClient(); - testRunnerServer = new TestRunnerServer + remoteEndpoint = new RemoteEndpointClient(endpointPort); + outOfProcessEndpointRunnerServer = new OutOfProcessEndpointRunnerServer ( + port: runnerPort, onEndpointStarted: e => { Logger.Info($"Received EndpointStarted event from remote endpoint '{e.EndpointName}'."); @@ -37,6 +39,13 @@ public OutOfProcessEndpointRunner(RunDescriptor runDescriptor, string endpointNa onOutgoingMessageOperation: operation => { ((IntegrationScenarioContext)runDescriptor.ScenarioContext).AddOutogingOperation(operation); + }, + onSetContextProperty: property => + { + ((IntegrationScenarioContext)runDescriptor.ScenarioContext).SetProperty( + property.EndpointName, + property.PropertyName, + property.PropertyValue); } ); @@ -213,6 +222,9 @@ public override async Task Stop() process.Dispose(); process = null; + await outOfProcessEndpointRunnerServer.Stop(); + outOfProcessEndpointRunnerServer = null; + errors.AddRange(GetFailedMessagesExceptions()); if (errors.Any()) diff --git a/src/protos/OutOfProcessEndpointRunner.proto b/src/protos/OutOfProcessEndpointRunner.proto new file mode 100644 index 00000000..976353c8 --- /dev/null +++ b/src/protos/OutOfProcessEndpointRunner.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "google/protobuf/any.proto"; + +option csharp_namespace = "NServiceBus.IntegrationTesting.OutOfProcess"; +package NServiceBus.IntegrationTesting.TestRunner; + +service OutOfProcessEndpointRunner { + rpc OnEndpointStarted (EndpointStartedEvent) returns (google.protobuf.Empty); + rpc RecordOutgoingMessageOperation (OutgoingMessageOperationEvent) returns (google.protobuf.Empty); + rpc SetContextProperty(SetContextPropertyRequest) returns (google.protobuf.Empty); +} + +message EndpointStartedEvent { + string endpointName = 1; +} + +message OutgoingMessageOperationEvent { + string operationTypeAssemblyQualifiedName = 1; + string senderEndpoint = 2; + string messageId = 3; + string messageTypeAssemblyQualifiedName = 4; + string messageInstanceJson = 5; + string messageHeadersJson = 6; + string operationErrorJson = 7; + string operationErrorTypeAssemblyQualifiedName = 8; +} + +message SetContextPropertyRequest{ + string propertyName = 1; + string propertyValue = 2; + string endpointName = 3; +} \ No newline at end of file diff --git a/src/protos/TestRunner.proto b/src/protos/TestRunner.proto deleted file mode 100644 index 60aad9c1..00000000 --- a/src/protos/TestRunner.proto +++ /dev/null @@ -1,21 +0,0 @@ -syntax = "proto3"; - -import "google/protobuf/empty.proto"; -import "google/protobuf/any.proto"; - -option csharp_namespace = "NServiceBus.IntegrationTesting.OutOfProcess"; -package NServiceBus.IntegrationTesting.TestRunner; - -service TestRunner { - rpc OnEndpointStarted (EndpointStartedEvent) returns (google.protobuf.Empty); - rpc RecordOutgoingMessageOperation (OutgoingMessageOperationEvent) returns (google.protobuf.Empty); -} - -message EndpointStartedEvent { - string endpointName = 1; -} - -message OutgoingMessageOperationEvent { - string jsonOperation = 1; - string jsonOperationType = 2; -} \ No newline at end of file From 2e4c1865d445b5de42cd514095442e3895c0986e Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 08:53:53 +0100 Subject: [PATCH 36/52] Check and skip instead of throwing --- .../AutoConfiguration.cs | 14 ++++++++++++-- .../CommandLine.cs | 7 ++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs index d3acafb9..bbaa0a31 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/AutoConfiguration.cs @@ -1,12 +1,22 @@ using Microsoft.Extensions.DependencyInjection; +using NServiceBus.Logging; namespace NServiceBus.IntegrationTesting.OutOfProcess.Nsb8 { class AutoConfiguration : INeedInitialization { + static ILog Logger = LogManager.GetLogger(); + public void Customize(EndpointConfiguration builder) { - CommandLine.EnsureIsIntegrationTest(); + if (!CommandLine.IsIntegrationTest()) + { + Logger.Warn("This package is designed for intergration testing when " + + "using NServiceBus.IntegrationTesting. Do not deploy this package " + + "to any environment. Skipping configuration."); + + return; + } var endpointName = CommandLine.GetEndpointName(); @@ -20,7 +30,7 @@ public void Customize(EndpointConfiguration builder) builder.EnableFeature(); builder.EnableFeature(); - builder.Pipeline.Register(new InterceptSendOperations(endpointName, testRunnerClient), "Intercept send operations streaming them to the remote test engine."); + builder.Pipeline.Register(new InterceptSendOperations(endpointName, testRunnerClient), "Intercept send operations reporting them to the remote test engine."); //builder.Pipeline.Register(new InterceptPublishOperations(endpointName, integrationScenarioContextClient), "Intercept publish operations reporting them to the remote test engine."); //builder.Pipeline.Register(new InterceptReplyOperations(endpointName, integrationScenarioContextClient), "Intercept reply operations reporting them to the remote test engine."); //builder.Pipeline.Register(new InterceptInvokedHandlers(endpointName, integrationScenarioContextClient), "Intercept invoked Message Handlers and Sagas reporting them to the remote test engine."); diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/CommandLine.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/CommandLine.cs index 03e95291..23f54fdb 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/CommandLine.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/CommandLine.cs @@ -7,17 +7,14 @@ namespace NServiceBus.IntegrationTesting.OutOfProcess { public static class CommandLine { - public static void EnsureIsIntegrationTest() + public static bool IsIntegrationTest() { var integrationTest = Environment.CommandLine .Split(' ') .Where(x => x == "--integrationTest") .SingleOrDefault(); - if (string.IsNullOrWhiteSpace(integrationTest)) - { - throw new ArgumentException("This library is designed for integration tests usage. If this is not an integration test remove the library dependency."); - } + return integrationTest != null; } public static string GetEndpointName() From 5103cca83dbd764eb2253eeccc1fdb1e61d097cb Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 12:16:29 +0100 Subject: [PATCH 37/52] Do not run Docker ps -a --- src/MySystem.AcceptanceTests/DockerCompose.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MySystem.AcceptanceTests/DockerCompose.cs b/src/MySystem.AcceptanceTests/DockerCompose.cs index 757cb70a..818b1f4f 100644 --- a/src/MySystem.AcceptanceTests/DockerCompose.cs +++ b/src/MySystem.AcceptanceTests/DockerCompose.cs @@ -17,7 +17,7 @@ public static class DockerCompose public static async Task Up() { Run("docker-compose", "up -d", workingDirectory: AppDomain.CurrentDomain.BaseDirectory); - Run("docker", "ps -a"); + //Run("docker", "ps -a"); static async Task statusChecker() { From 84d97bc341387adb2862b8e8c123ba9e855692aa Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 12:52:51 +0100 Subject: [PATCH 38/52] Revert "Do not run Docker ps -a" This reverts commit 24feb2d342bfa2651546cb2a7915a7a8a9b037fb. --- src/MySystem.AcceptanceTests/DockerCompose.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MySystem.AcceptanceTests/DockerCompose.cs b/src/MySystem.AcceptanceTests/DockerCompose.cs index 818b1f4f..757cb70a 100644 --- a/src/MySystem.AcceptanceTests/DockerCompose.cs +++ b/src/MySystem.AcceptanceTests/DockerCompose.cs @@ -17,7 +17,7 @@ public static class DockerCompose public static async Task Up() { Run("docker-compose", "up -d", workingDirectory: AppDomain.CurrentDomain.BaseDirectory); - //Run("docker", "ps -a"); + Run("docker", "ps -a"); static async Task statusChecker() { From 2aec7a1d94b6aed4c49dfce1fc1b41c5a6b9e526 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 13:40:30 +0100 Subject: [PATCH 39/52] Try to use Windows latest --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14dfcce4..de963bc2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: include: - - os: windows-2019 + - os: windows-latest name: Windows - os: ubuntu-20.04 name: Linux From 276ac9705fb143a4e9c199c22a151dad05b72ede Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 14:00:04 +0100 Subject: [PATCH 40/52] Use external RabbitMQ --- .github/workflows/ci.yml | 9 +++ README.md | 4 ++ src/MySystem.AcceptanceTests/DockerCompose.cs | 55 ------------------- .../When_sending_ALonleyMessage.cs | 12 ---- .../When_sending_AMessage.cs | 12 ---- .../When_sending_CompleteASaga.cs | 12 ---- .../docker-compose.yml | 12 ---- 7 files changed, 13 insertions(+), 103 deletions(-) delete mode 100644 src/MySystem.AcceptanceTests/DockerCompose.cs delete mode 100644 src/MySystem.AcceptanceTests/docker-compose.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de963bc2..7c232768 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,15 @@ on: env: DOTNET_NOLOGO: true jobs: + setup-rabbit: + name: Setup RabbitMQ + runs-on: ubuntu-latest + services: + rabbit: + image: rabbitmq:3.8-management + ports: + - 5672:5672 + - 15672:15672 build: name: Build and test on ${{ matrix.name }} runs-on: ${{ matrix.os }} diff --git a/README.md b/README.md index fb2c352a..4ce0112a 100644 --- a/README.md +++ b/README.md @@ -455,6 +455,10 @@ The above extension method is far from being complete and doesn't handle all the Using a package manager add a NuGet reference to [NServiceBus.IntegrationTesting](https://www.nuget.org/packages/NServiceBus.IntegrationTesting/). +## Running tests + +Running tests requires a RabbitMQ instance listening on the default 5672 port. + ## Background For more information on the genesis of this package refer to the [Exploring NServiceBus Integration Testing options](https://milestone.topics.it/2019/07/04/exploring-nservicebus-integration-testing-options.html) article. diff --git a/src/MySystem.AcceptanceTests/DockerCompose.cs b/src/MySystem.AcceptanceTests/DockerCompose.cs deleted file mode 100644 index 757cb70a..00000000 --- a/src/MySystem.AcceptanceTests/DockerCompose.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Threading.Tasks; -using static SimpleExec.Command; - -#if NETCOREAPP -using System.Net.Http; -#endif - -#if NET48 -using System.Net; -#endif - -namespace MySystem.AcceptanceTests -{ - public static class DockerCompose - { - public static async Task Up() - { - Run("docker-compose", "up -d", workingDirectory: AppDomain.CurrentDomain.BaseDirectory); - Run("docker", "ps -a"); - - static async Task statusChecker() - { - try - { - var managementUrl = "http://localhost:15672/"; -#if NETCOREAPP - using var client = new HttpClient(); - var response = await client.GetAsync(managementUrl); - return response.IsSuccessStatusCode; -#endif - -#if NET48 - var request = HttpWebRequest.Create(managementUrl); - var response = await request.GetResponseAsync(); - return ((HttpWebResponse)response).StatusCode == HttpStatusCode.OK; -#endif - } - catch - { - return false; - } - } - while (!await statusChecker()) - { - await Task.Delay(500); - } - } - - public static void Down() - { - Run("docker-compose", "down", workingDirectory: AppDomain.CurrentDomain.BaseDirectory); - } - } -} diff --git a/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs b/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs index eff8d21f..c7726ef9 100644 --- a/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs +++ b/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs @@ -11,18 +11,6 @@ namespace MySystem.AcceptanceTests { public class When_sending_ALonleyMessage_out_of_process { - [OneTimeSetUp] - public async Task Setup() - { - await DockerCompose.Up(); - } - - [OneTimeTearDown] - public void Teardown() - { - DockerCompose.Down(); - } - [Test] public async Task The_message_is_sent_as_expected() { diff --git a/src/MySystem.AcceptanceTests/When_sending_AMessage.cs b/src/MySystem.AcceptanceTests/When_sending_AMessage.cs index 82299625..5c8843c1 100644 --- a/src/MySystem.AcceptanceTests/When_sending_AMessage.cs +++ b/src/MySystem.AcceptanceTests/When_sending_AMessage.cs @@ -11,18 +11,6 @@ namespace MySystem.AcceptanceTests { public class When_sending_AMessage { - [OneTimeSetUp] - public async Task Setup() - { - await DockerCompose.Up(); - } - - [OneTimeTearDown] - public void Teardown() - { - DockerCompose.Down(); - } - class Context : IntegrationScenarioContext { public bool RemoteWhenExecuted { get; set; } diff --git a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs index f0814548..1ddb710f 100644 --- a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs +++ b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs @@ -11,18 +11,6 @@ namespace MySystem.AcceptanceTests { public class When_sending_CompleteASaga { - [OneTimeSetUp] - public async Task Setup() - { - await DockerCompose.Up(); - } - - [OneTimeTearDown] - public void Teardown() - { - DockerCompose.Down(); - } - class Context : IntegrationScenarioContext { public bool WhenExecuted { get; set; } diff --git a/src/MySystem.AcceptanceTests/docker-compose.yml b/src/MySystem.AcceptanceTests/docker-compose.yml deleted file mode 100644 index 68e46b68..00000000 --- a/src/MySystem.AcceptanceTests/docker-compose.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: '2.0' -services: - rabbit: - container_name: mysystem_acceptancetests_rabbit - hostname: rabbit - image: rabbitmq:3.8-management - environment: - - RABBITMQ_DEFAULT_USER=guest - - RABBITMQ_DEFAULT_PASS=guest - ports: - - "5672:5672" - - "15672:15672" \ No newline at end of file From 0e29a238a1822df1b96fc9ca0ed4f94c9c7190e4 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 14:09:36 +0100 Subject: [PATCH 41/52] Use an external action to setup RabbitMQ (I bet it fails...) --- .github/workflows/ci.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c232768..14293dc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,15 +12,6 @@ on: env: DOTNET_NOLOGO: true jobs: - setup-rabbit: - name: Setup RabbitMQ - runs-on: ubuntu-latest - services: - rabbit: - image: rabbitmq:3.8-management - ports: - - 5672:5672 - - 15672:15672 build: name: Build and test on ${{ matrix.name }} runs-on: ${{ matrix.os }} @@ -32,6 +23,13 @@ jobs: - os: ubuntu-20.04 name: Linux fail-fast: false + steps: + - uses: getong/rabbitmq-action@v1.2 + with: + rabbitmq version: '3.8' + host port: 5672 + rabbitmq user: 'guest' + rabbitmq password: 'guest' steps: - name: Checkout uses: actions/checkout@v2.4.0 From a9daebe057a3fff736ccaf3303d136807d3e85f1 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 14:10:31 +0100 Subject: [PATCH 42/52] ooops --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14293dc5..9b18b30c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,6 @@ jobs: host port: 5672 rabbitmq user: 'guest' rabbitmq password: 'guest' - steps: - name: Checkout uses: actions/checkout@v2.4.0 with: From 34ac64680b3eab43e81626910197ba833483c7c6 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 14:27:14 +0100 Subject: [PATCH 43/52] Do not setup RabbitMQ (not supported on Windows) --- .github/workflows/ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b18b30c..de963bc2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,12 +24,6 @@ jobs: name: Linux fail-fast: false steps: - - uses: getong/rabbitmq-action@v1.2 - with: - rabbitmq version: '3.8' - host port: 5672 - rabbitmq user: 'guest' - rabbitmq password: 'guest' - name: Checkout uses: actions/checkout@v2.4.0 with: From 6750df1690c1d700e6f4d59b8cff2053ef336a3e Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 14:33:38 +0100 Subject: [PATCH 44/52] Use the learning transport for tests (containers are not supported on Windows agents) --- src/MyOtherService/MyOtherService.csproj | 1 - src/MyOtherService/MyOtherServiceConfigurationBuilder.cs | 2 +- src/MyService/MyService.csproj | 1 - src/MyService/MyServiceConfiguration.cs | 2 +- src/Snippets/ConfigurationSnippets.cs | 6 +++--- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/MyOtherService/MyOtherService.csproj b/src/MyOtherService/MyOtherService.csproj index 4dce04a1..0ab807e7 100644 --- a/src/MyOtherService/MyOtherService.csproj +++ b/src/MyOtherService/MyOtherService.csproj @@ -9,7 +9,6 @@ - diff --git a/src/MyOtherService/MyOtherServiceConfigurationBuilder.cs b/src/MyOtherService/MyOtherServiceConfigurationBuilder.cs index 0500cd4f..aa42495f 100644 --- a/src/MyOtherService/MyOtherServiceConfigurationBuilder.cs +++ b/src/MyOtherService/MyOtherServiceConfigurationBuilder.cs @@ -14,7 +14,7 @@ public static EndpointConfiguration Build(string endpointName, string rabbitMqCo config.UsePersistence(); config.EnableInstallers(); - var transport = new RabbitMQTransport(Topology.Conventional, rabbitMqConnectionString); + var transport = new LearningTransport(); config.UseTransport(transport); config.SendFailedMessagesTo("error"); diff --git a/src/MyService/MyService.csproj b/src/MyService/MyService.csproj index 6edf6385..b8321f0e 100644 --- a/src/MyService/MyService.csproj +++ b/src/MyService/MyService.csproj @@ -8,7 +8,6 @@ - diff --git a/src/MyService/MyServiceConfiguration.cs b/src/MyService/MyServiceConfiguration.cs index 03fa7917..b854a652 100644 --- a/src/MyService/MyServiceConfiguration.cs +++ b/src/MyService/MyServiceConfiguration.cs @@ -14,7 +14,7 @@ public MyServiceConfiguration() this.UsePersistence(); this.EnableInstallers(); - var transport = new RabbitMQTransport(Topology.Conventional, "host=localhost;username=guest;password=guest"); + var transport = new LearningTransport(); var routing = this.UseTransport(transport); routing.RouteToEndpoint(typeof(AMessage), "MyOtherService"); diff --git a/src/Snippets/ConfigurationSnippets.cs b/src/Snippets/ConfigurationSnippets.cs index ae9c3ee8..1995f1c8 100644 --- a/src/Snippets/ConfigurationSnippets.cs +++ b/src/Snippets/ConfigurationSnippets.cs @@ -11,7 +11,7 @@ public MyServiceConfiguration() this.SendFailedMessagesTo("error"); this.EnableInstallers(); - this.UseTransport(new RabbitMQTransport(Topology.Conventional, "host=localhost")); + this.UseTransport(new LearningTransport()); } } // end-snippet @@ -19,13 +19,13 @@ public MyServiceConfiguration() // begin-snippet: use-builder-class public static class MyServiceConfigurationBuilder { - public static EndpointConfiguration Build(string endpointName, string rabbitMqConnectionString) + public static EndpointConfiguration Build(string endpointName) { var config = new EndpointConfiguration(endpointName); config.SendFailedMessagesTo("error"); config.EnableInstallers(); - config.UseTransport(new RabbitMQTransport(Topology.Conventional, rabbitMqConnectionString)); + config.UseTransport(new LearningTransport()); return config; } From cffad8b1313bd1aa8b5099c5f4d65a06e6480f89 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 14:36:51 +0100 Subject: [PATCH 45/52] Remove tests requirements --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 4ce0112a..fb2c352a 100644 --- a/README.md +++ b/README.md @@ -455,10 +455,6 @@ The above extension method is far from being complete and doesn't handle all the Using a package manager add a NuGet reference to [NServiceBus.IntegrationTesting](https://www.nuget.org/packages/NServiceBus.IntegrationTesting/). -## Running tests - -Running tests requires a RabbitMQ instance listening on the default 5672 port. - ## Background For more information on the genesis of this package refer to the [Exploring NServiceBus Integration Testing options](https://milestone.topics.it/2019/07/04/exploring-nservicebus-integration-testing-options.html) article. From 1d2bc46804b0df1c79fa26b2037b96de4bb5006f Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 14:53:53 +0100 Subject: [PATCH 46/52] Note about supported transports --- src/Snippets/ConfigurationSnippets.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Snippets/ConfigurationSnippets.cs b/src/Snippets/ConfigurationSnippets.cs index 1995f1c8..f35923d9 100644 --- a/src/Snippets/ConfigurationSnippets.cs +++ b/src/Snippets/ConfigurationSnippets.cs @@ -11,6 +11,10 @@ public MyServiceConfiguration() this.SendFailedMessagesTo("error"); this.EnableInstallers(); + /* + * Any NServiceBus suppported transport can be used. Tests in this + * repostory are using the LearningTransport for the setup simplicity + */ this.UseTransport(new LearningTransport()); } } @@ -25,6 +29,10 @@ public static EndpointConfiguration Build(string endpointName) config.SendFailedMessagesTo("error"); config.EnableInstallers(); + /* + * Any NServiceBus suppported transport can be used. Tests in this + * repostory are using the LearningTransport for the setup simplicity + */ config.UseTransport(new LearningTransport()); return config; From b71e8b551d2aca6190dd737f0f59e40d714a1d32 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 8 Jan 2022 14:42:21 +0000 Subject: [PATCH 47/52] MarkdownSnippets documentation changes --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fb2c352a..62d75273 100644 --- a/README.md +++ b/README.md @@ -109,11 +109,15 @@ public class MyServiceConfiguration : EndpointConfiguration this.SendFailedMessagesTo("error"); this.EnableInstallers(); - this.UseTransport(new RabbitMQTransport(Topology.Conventional, "host=localhost")); + /* + * Any NServiceBus suppported transport can be used. Tests in this + * repostory are using the LearningTransport for the setup simplicity + */ + this.UseTransport(new LearningTransport()); } } ``` -snippet source | anchor +snippet source | anchor Using the above approach can be problematic when configuration values need to be read from an external source, like for example a configuration file. If this is the case the same external configuration source, most of the times with different values, needs to be available in tests too. @@ -127,19 +131,23 @@ In case configuration values need to be passed to the endpoint configuration the ```cs public static class MyServiceConfigurationBuilder { - public static EndpointConfiguration Build(string endpointName, string rabbitMqConnectionString) + public static EndpointConfiguration Build(string endpointName) { var config = new EndpointConfiguration(endpointName); config.SendFailedMessagesTo("error"); config.EnableInstallers(); - config.UseTransport(new RabbitMQTransport(Topology.Conventional, rabbitMqConnectionString)); + /* + * Any NServiceBus suppported transport can be used. Tests in this + * repostory are using the LearningTransport for the setup simplicity + */ + config.UseTransport(new LearningTransport()); return config; } } ``` -snippet source | anchor +snippet source | anchor ### Define endpoints used in each test From 493c4cf6f1bb79b222e783bb68bafe147eab761b Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 20:29:34 +0100 Subject: [PATCH 48/52] Introduce the concept of remote operations and do not try to reuse existing operation classes --- .../When_sending_ALonleyMessage.cs | 6 +- ...ServiceBus.IntegrationTesting.Model.csproj | 7 -- .../InterceptInvokedHandlers.cs | 94 +++++++++++++++++++ .../InterceptSendOperations.cs | 61 +++++++++--- ...Bus.IntegrationTesting.OutOfProcess.csproj | 4 - .../OutOfProcessEndpointRunnerClient.cs | 37 +++----- .../OutOfProcessEndpointRunnerImpl.cs | 28 +----- .../OutOfProcessEndpointRunnerServer.cs | 8 +- src/NServiceBus.IntegrationTesting.sln | 6 -- .../IntegrationScenarioContext.cs | 8 ++ .../Invocations.cs | 0 .../NServiceBus.IntegrationTesting.csproj | 1 - .../OutOfProcessEndpointRunner.cs | 4 +- .../OutgoingMessages.cs | 0 src/protos/OutOfProcessEndpointRunner.proto | 35 ++++++- 15 files changed, 211 insertions(+), 88 deletions(-) delete mode 100644 src/NServiceBus.IntegrationTesting.Model/NServiceBus.IntegrationTesting.Model.csproj create mode 100644 src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptInvokedHandlers.cs rename src/{NServiceBus.IntegrationTesting.Model => NServiceBus.IntegrationTesting}/Invocations.cs (100%) rename src/{NServiceBus.IntegrationTesting.Model => NServiceBus.IntegrationTesting}/OutgoingMessages.cs (100%) diff --git a/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs b/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs index c7726ef9..2da8966b 100644 --- a/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs +++ b/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs @@ -21,10 +21,10 @@ public async Task The_message_is_sent_as_expected() .WithOutOfProcessEndpoint(EndpointName, EndpointReference.FromSolutionProject("MyService.TestProxy"), behavior => { behavior.When( - //condition: ctx => + //condition: ctx => //{ // return ctx.GetProperties(EndpointName).ContainsKey(Properties.DebuggerAttached); - //}, + //}, action: remoteSession => { return remoteSession.Send(EndpointName, new ALonleyMessage() { AnIdentifier = theExpectedIdentifier }); @@ -32,7 +32,7 @@ public async Task The_message_is_sent_as_expected() }) .Done(c => { - return c.OutgoingMessageOperations.Any(op => op.MessageType == typeof(ALonleyMessage)) || c.HasFailedMessages(); + return c.RemoteSendMessageOperations.Any(op => op.MessageTypeAssemblyQualifiedName == typeof(ALonleyMessage).AssemblyQualifiedName) || c.HasFailedMessages(); }) .Run(); diff --git a/src/NServiceBus.IntegrationTesting.Model/NServiceBus.IntegrationTesting.Model.csproj b/src/NServiceBus.IntegrationTesting.Model/NServiceBus.IntegrationTesting.Model.csproj deleted file mode 100644 index 9f5c4f4a..00000000 --- a/src/NServiceBus.IntegrationTesting.Model/NServiceBus.IntegrationTesting.Model.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - netstandard2.0 - - - diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptInvokedHandlers.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptInvokedHandlers.cs new file mode 100644 index 00000000..4c5247bb --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptInvokedHandlers.cs @@ -0,0 +1,94 @@ +//using NServiceBus.IntegrationTesting.OutOfProcess; +//using NServiceBus.Pipeline; +//using NServiceBus.Sagas; +//using System; +//using System.Threading.Tasks; + +//namespace NServiceBus.IntegrationTesting +//{ +// class InterceptInvokedHandlers : Behavior +// { +// readonly string endpointName; +// readonly OutOfProcessEndpointRunnerClient testRunnerClient; + +// public InterceptInvokedHandlers(string endpointName, OutOfProcessEndpointRunnerClient testRunnerClient) +// { +// this.endpointName = endpointName; +// this.testRunnerClient = testRunnerClient; +// } + +// public override async Task Invoke(IInvokeHandlerContext context, Func next) +// { +// try +// { +// await next(); +// CaptureInvocation(context, null); +// } +// catch (Exception ex) +// { +// CaptureInvocation(context, ex); +// throw; +// } +// } + +// void CaptureInvocation(IInvokeHandlerContext context, Exception handlingError) +// { +// //var request = new InvokedHandlerEvent() +// //{ +// // EndpointName = handlerInvocation.EndpointName, +// // HandlerTypeAssemblyQualifiedName = handlerInvocation.HandlerType.AssemblyQualifiedName, +// // MessageInstanceJson = JsonSerializer.Serialize(handlerInvocation.Message), +// // MessageTypeAssemblyQualifiedName = handlerInvocation.MessageType.AssemblyQualifiedName, +// // HandlingErrorJson = handlerInvocation.HandlingError != null +// // ? JsonSerializer.Serialize(handlerInvocation.HandlingError) +// // : string.Empty, +// // HandlingErrorTypeAssemblyQualifiedName = handlerInvocation.HandlingError != null +// // ? handlerInvocation.HandlingError.GetType().AssemblyQualifiedName +// // : string.Empty, +// //}; + +// try +// { +// if (context.Extensions.TryGet(out ActiveSagaInstance saga)) +// { +// //var sagaInvocation = saga.NotFound +// // ? new SagaInvocation() +// // { +// // NotFound = true +// // } +// // : new SagaInvocation() +// // { +// // NotFound = false, +// // SagaType = saga.Instance.GetType(), +// // IsNew = saga.IsNew, +// // IsCompleted = saga.Instance.Completed, +// // SagaData = saga.Instance.Entity +// // }; + +// //FillCommonDetails(context, handlingError, sagaInvocation); + +// //await testRunnerClient.RecordInvokedSaga(sagaInvocation); +// } +// else +// { +// var handlerInvocation = new HandlerInvocation() { HandlerType = context.MessageHandler.HandlerType }; +// FillCommonDetails(context, handlingError, handlerInvocation); + +// await testRunnerClient.RecordInvokedHandler(handlerInvocation); +// } +// } +// catch (Exception e) +// { +// throw new InvalidOperationException("Testing infrastructure failure while capturing handler/saga invocation.", e); +// } +// } + +// void FillCommonDetails(IInvokeHandlerContext context, Exception handlingError, Invocation invocation) +// { +// invocation.EndpointName = endpointName; +// invocation.Message = context.MessageBeingHandled; +// invocation.MessageType = context.MessageMetadata.MessageType; +// invocation.HandlingError = handlingError; +// } +// } +//} \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs index b1f6da21..84118a99 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs @@ -1,5 +1,7 @@ using NServiceBus.Pipeline; using System; +using System.Linq; +using System.Text.Json; using System.Threading.Tasks; namespace NServiceBus.IntegrationTesting.OutOfProcess @@ -17,27 +19,43 @@ public InterceptSendOperations(string endpointName, OutOfProcessEndpointRunnerCl public override async Task Invoke(IOutgoingSendContext context, Func next) { - OutgoingMessageOperation outgoingOperation; + RemoteRequestTimeoutOperation requestTimeoutOp = null; + RemoteSendMessageOperation sendOp = null; + if (context.Headers.ContainsKey(Headers.IsSagaTimeoutMessage) && context.Headers[Headers.IsSagaTimeoutMessage] == bool.TrueString) { - outgoingOperation = new RequestTimeoutOperation() + requestTimeoutOp = new RemoteRequestTimeoutOperation() { + SenderEndpoint = endpointName, + MessageInstanceJson = JsonSerializer.Serialize(context.Message.Instance, context.Message.MessageType), + MessageId = context.MessageId, + MessageTypeAssemblyQualifiedName = context.Message.MessageType.AssemblyQualifiedName, SagaId = context.Headers[Headers.SagaId], SagaTypeAssemblyQualifiedName = context.Headers[Headers.SagaType] }; + requestTimeoutOp.MessageHeaders.AddRange(context.Headers.Select(kvp => new Header() + { + Key = kvp.Key, + Value = kvp.Value ?? "" + })); } else { - outgoingOperation = new SendOperation(); + sendOp = new RemoteSendMessageOperation() + { + SenderEndpoint = endpointName, + MessageInstanceJson = JsonSerializer.Serialize(context.Message.Instance, context.Message.MessageType), + MessageId = context.MessageId, + MessageTypeAssemblyQualifiedName = context.Message.MessageType.AssemblyQualifiedName + }; + sendOp.MessageHeaders.AddRange(context.Headers.Select(kvp => new Header() + { + Key = kvp.Key, + Value = kvp.Value ?? "" + })); } - outgoingOperation.SenderEndpoint = endpointName; - outgoingOperation.MessageId = context.MessageId; - outgoingOperation.MessageType = context.Message.MessageType; - outgoingOperation.MessageInstance = context.Message.Instance; - outgoingOperation.MessageHeaders = context.Headers; - - await testRunnerClient.RecordOutgoingMessageOperation(outgoingOperation); + Exception ex = null; try { @@ -45,9 +63,30 @@ public override async Task Invoke(IOutgoingSendContext context, Func next) } catch (Exception sendError) { - outgoingOperation.OperationError = sendError; + ex = sendError; throw; } + finally + { + if (context.Headers.ContainsKey(Headers.IsSagaTimeoutMessage) && context.Headers[Headers.IsSagaTimeoutMessage] == bool.TrueString) + { + if (ex != null) + { + requestTimeoutOp.OperationErrorTypeAssemblyQualifiedName = ex.GetType().AssemblyQualifiedName; + requestTimeoutOp.OperationErrorJson = JsonSerializer.Serialize(ex); + } + await testRunnerClient.RecordRequestTimeoutOperation(requestTimeoutOp); + } + else + { + if (ex != null) + { + sendOp.OperationErrorTypeAssemblyQualifiedName = ex.GetType().AssemblyQualifiedName; + sendOp.OperationErrorJson = JsonSerializer.Serialize(ex); + } + await testRunnerClient.RecordSendMessageOperation(sendOp); + } + } } } } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj b/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj index bdc2a855..56ab8ca2 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/NServiceBus.IntegrationTesting.OutOfProcess.csproj @@ -10,10 +10,6 @@ - - - - diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerClient.cs index 498a8906..bbacedb7 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerClient.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerClient.cs @@ -1,4 +1,6 @@ using Grpc.Core; +using System; +using System.Text.Json; using System.Threading.Tasks; namespace NServiceBus.IntegrationTesting.OutOfProcess @@ -25,30 +27,19 @@ public async Task OnEndpointStarted() }); } - public async Task RecordOutgoingMessageOperation(OutgoingMessageOperation outgoingMessageOperation) + public async Task RecordSendMessageOperation(RemoteSendMessageOperation request) { - var request = new OutgoingMessageOperationEvent() - { - OperationTypeAssemblyQualifiedName = outgoingMessageOperation.GetType().AssemblyQualifiedName, - SenderEndpoint = outgoingMessageOperation.SenderEndpoint, - //MessageInstanceJson = outgoingMessageOperation.MessageInstance != null - // ? JsonSerializer.Serialize(outgoingMessageOperation.MessageInstance) - // : null, - MessageId = outgoingMessageOperation.MessageId, - //MessageHeadersJson = outgoingMessageOperation.MessageHeaders != null - // ? JsonSerializer.Serialize(outgoingMessageOperation.MessageHeaders) - // : null, - MessageTypeAssemblyQualifiedName = outgoingMessageOperation.MessageType != null - ? outgoingMessageOperation.MessageType.AssemblyQualifiedName - : null, - //OperationErrorTypeAssemblyQualifiedName = outgoingMessageOperation.OperationError != null - // ? outgoingMessageOperation.OperationError.GetType().AssemblyQualifiedName - // : null, - //OperationErrorJson = outgoingMessageOperation.OperationError != null - // ? JsonSerializer.Serialize(outgoingMessageOperation.OperationError) - // : null - }; - _ = await client.RecordOutgoingMessageOperationAsync(request); + _ = await client.RecordSendMessageOperationAsync(request); + } + + public async Task RecordRequestTimeoutOperation(RemoteRequestTimeoutOperation request) + { + _ = await client.RecordRequestTimeoutOperationAsync(request); + } + + public async Task RecordInvokedHandler(RemoteHandlerInvocation request) + { + _ = await client.RecordInvokedHandlerAsync(request); } public async Task SetContextProperty(string propertyName, string propertyValue) diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerImpl.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerImpl.cs index 3919a6b5..b0451cab 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerImpl.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerImpl.cs @@ -11,17 +11,17 @@ namespace NServiceBus.IntegrationTesting.OutOfProcess class OutOfProcessEndpointRunnerImpl : OutOfProcessEndpointRunnerBase { readonly Action onEndpointStarted; - readonly Action onOutgoingMessageOperation; + readonly Action onRemoteSendMessageOperation; private readonly Action<(string EndpointName, string PropertyName, string PropertyValue)> onSetContextProperty; public OutOfProcessEndpointRunnerImpl( Action onEndpointStarted, - Action onOutgoingMessageOperation, + Action onRemoteSendMessageOperation, Action<(string EndpointName, string PropertyName, string PropertyValue)> onSetContextProperty ) { this.onEndpointStarted = onEndpointStarted; - this.onOutgoingMessageOperation = onOutgoingMessageOperation; + this.onRemoteSendMessageOperation = onRemoteSendMessageOperation; this.onSetContextProperty = onSetContextProperty; } @@ -32,27 +32,9 @@ public override Task OnEndpointStarted(EndpointStartedEvent request, Serv return Task.FromResult(new Empty()); } - public override Task RecordOutgoingMessageOperation(OutgoingMessageOperationEvent request, ServerCallContext context) + public override Task RecordSendMessageOperation(RemoteSendMessageOperation request, ServerCallContext context) { - var operationType = System.Type.GetType(request.OperationTypeAssemblyQualifiedName); - var operation = (OutgoingMessageOperation)Activator.CreateInstance(operationType); - - operation.SenderEndpoint = request.SenderEndpoint; - operation.MessageId = request.MessageId; - operation.MessageType = !string.IsNullOrWhiteSpace(request.MessageTypeAssemblyQualifiedName) - ? System.Type.GetType(request.MessageTypeAssemblyQualifiedName) - : null; - operation.MessageInstance = !string.IsNullOrWhiteSpace(request.MessageInstanceJson) - ? JsonSerializer.Deserialize(request.MessageInstanceJson, operation.MessageType) - : null; - operation.MessageHeaders = !string.IsNullOrWhiteSpace(request.MessageHeadersJson) - ? (Dictionary)JsonSerializer.Deserialize(request.MessageHeadersJson, typeof(Dictionary)) - : null; - operation.OperationError = !string.IsNullOrWhiteSpace(request.OperationErrorJson) - ? (Exception)JsonSerializer.Deserialize(request.OperationErrorJson, System.Type.GetType(request.OperationErrorTypeAssemblyQualifiedName)) - : null; - - onOutgoingMessageOperation(operation); + onRemoteSendMessageOperation(request); return Task.FromResult(new Empty()); } diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerServer.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerServer.cs index 2e212843..ef3fd923 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerServer.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerServer.cs @@ -11,13 +11,13 @@ public class OutOfProcessEndpointRunnerServer public OutOfProcessEndpointRunnerServer( int port, - Action onEndpointStarted, - Action onOutgoingMessageOperation, + Action onEndpointStarted, + Action onRemoteSendMessageOperation, Action<(string EndpointName, string PropertyName, string PropertyValue)> onSetContextProperty) { testRunner = new OutOfProcessEndpointRunnerImpl( - onEndpointStarted, - onOutgoingMessageOperation, + onEndpointStarted, + onRemoteSendMessageOperation, onSetContextProperty); server = new Server diff --git a/src/NServiceBus.IntegrationTesting.sln b/src/NServiceBus.IntegrationTesting.sln index b95bed01..2d06a1ed 100644 --- a/src/NServiceBus.IntegrationTesting.sln +++ b/src/NServiceBus.IntegrationTesting.sln @@ -27,8 +27,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.IntegrationTest EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.IntegrationTesting.OutOfProcess", "NServiceBus.IntegrationTesting.OutOfProcess\NServiceBus.IntegrationTesting.OutOfProcess.csproj", "{AEF55741-1636-427C-8CD0-AF6494B8F9A7}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.IntegrationTesting.Model", "NServiceBus.IntegrationTesting.Model\NServiceBus.IntegrationTesting.Model.csproj", "{ABC00FEA-AE54-43F5-BCFC-85AEEA90E006}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -83,10 +81,6 @@ Global {AEF55741-1636-427C-8CD0-AF6494B8F9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU {AEF55741-1636-427C-8CD0-AF6494B8F9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU {AEF55741-1636-427C-8CD0-AF6494B8F9A7}.Release|Any CPU.Build.0 = Release|Any CPU - {ABC00FEA-AE54-43F5-BCFC-85AEEA90E006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ABC00FEA-AE54-43F5-BCFC-85AEEA90E006}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ABC00FEA-AE54-43F5-BCFC-85AEEA90E006}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ABC00FEA-AE54-43F5-BCFC-85AEEA90E006}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs b/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs index 4d071c62..8c6e971a 100644 --- a/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs +++ b/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs @@ -1,5 +1,6 @@ using NServiceBus.AcceptanceTesting; using NServiceBus.DelayedDelivery; +using NServiceBus.IntegrationTesting.OutOfProcess; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -14,6 +15,7 @@ public class IntegrationScenarioContext : ScenarioContext readonly ConcurrentBag invokedHandlers = new(); readonly ConcurrentBag invokedSagas = new(); readonly ConcurrentBag outgoingMessageOperations = new(); + readonly ConcurrentBag remoteSendMessageOperations = new(); readonly Dictionary> timeoutRescheduleRules = new(); readonly Dictionary> properties = new(); readonly Dictionary ports = new(); @@ -42,8 +44,14 @@ public class IntegrationScenarioContext : ScenarioContext return newPorts; } + internal void AddRemoteOperation(RemoteSendMessageOperation operation) + { + remoteSendMessageOperations.Add(operation); + } + public IEnumerable InvokedSagas { get { return invokedSagas; } } public IEnumerable OutgoingMessageOperations { get { return outgoingMessageOperations; } } + public IEnumerable RemoteSendMessageOperations { get { return remoteSendMessageOperations; } } public IReadOnlyDictionary GetProperties(string endpointName) { diff --git a/src/NServiceBus.IntegrationTesting.Model/Invocations.cs b/src/NServiceBus.IntegrationTesting/Invocations.cs similarity index 100% rename from src/NServiceBus.IntegrationTesting.Model/Invocations.cs rename to src/NServiceBus.IntegrationTesting/Invocations.cs diff --git a/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj b/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj index d6163ac0..b0f5156a 100644 --- a/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj +++ b/src/NServiceBus.IntegrationTesting/NServiceBus.IntegrationTesting.csproj @@ -38,7 +38,6 @@ - diff --git a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs index 6feff6c3..4b548d60 100644 --- a/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs +++ b/src/NServiceBus.IntegrationTesting/OutOfProcessEndpointRunner.cs @@ -36,9 +36,9 @@ public OutOfProcessEndpointRunner(RunDescriptor runDescriptor, string endpointNa Logger.Info($"Received EndpointStarted event from remote endpoint '{e.EndpointName}'."); remoteEndpointStarted = true; }, - onOutgoingMessageOperation: operation => + onRemoteSendMessageOperation: operation => { - ((IntegrationScenarioContext)runDescriptor.ScenarioContext).AddOutogingOperation(operation); + ((IntegrationScenarioContext)runDescriptor.ScenarioContext).AddRemoteOperation(operation); }, onSetContextProperty: property => { diff --git a/src/NServiceBus.IntegrationTesting.Model/OutgoingMessages.cs b/src/NServiceBus.IntegrationTesting/OutgoingMessages.cs similarity index 100% rename from src/NServiceBus.IntegrationTesting.Model/OutgoingMessages.cs rename to src/NServiceBus.IntegrationTesting/OutgoingMessages.cs diff --git a/src/protos/OutOfProcessEndpointRunner.proto b/src/protos/OutOfProcessEndpointRunner.proto index 976353c8..a359d8d3 100644 --- a/src/protos/OutOfProcessEndpointRunner.proto +++ b/src/protos/OutOfProcessEndpointRunner.proto @@ -8,27 +8,54 @@ package NServiceBus.IntegrationTesting.TestRunner; service OutOfProcessEndpointRunner { rpc OnEndpointStarted (EndpointStartedEvent) returns (google.protobuf.Empty); - rpc RecordOutgoingMessageOperation (OutgoingMessageOperationEvent) returns (google.protobuf.Empty); rpc SetContextProperty(SetContextPropertyRequest) returns (google.protobuf.Empty); + rpc RecordSendMessageOperation (RemoteSendMessageOperation) returns (google.protobuf.Empty); + rpc RecordRequestTimeoutOperation (RemoteRequestTimeoutOperation) returns (google.protobuf.Empty); + rpc RecordInvokedHandler(RemoteHandlerInvocation) returns (google.protobuf.Empty); } message EndpointStartedEvent { string endpointName = 1; } -message OutgoingMessageOperationEvent { - string operationTypeAssemblyQualifiedName = 1; +message RemoteSendMessageOperation { string senderEndpoint = 2; string messageId = 3; string messageTypeAssemblyQualifiedName = 4; string messageInstanceJson = 5; - string messageHeadersJson = 6; + repeated Header messageHeaders = 6; string operationErrorJson = 7; string operationErrorTypeAssemblyQualifiedName = 8; } +message RemoteRequestTimeoutOperation { + string senderEndpoint = 2; + string messageId = 3; + string messageTypeAssemblyQualifiedName = 4; + string messageInstanceJson = 5; + repeated Header messageHeaders = 6; + string operationErrorJson = 7; + string operationErrorTypeAssemblyQualifiedName = 8; + string sagaId = 9; + string sagaTypeAssemblyQualifiedName = 10; +} + +message RemoteHandlerInvocation{ + string handlerTypeAssemblyQualifiedName = 1; + string endpointName = 2; + string messageTypeAssemblyQualifiedName = 3; + string messageInstanceJson = 4; + string handlingErrorJson = 5; + string handlingErrorTypeAssemblyQualifiedName = 6; +} + message SetContextPropertyRequest{ string propertyName = 1; string propertyValue = 2; string endpointName = 3; +} + +message Header{ + string key = 1; + string value = 2; } \ No newline at end of file From d8053df05091815ec51dd729449076719890d2d9 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jan 2022 20:33:47 +0100 Subject: [PATCH 49/52] Approved API --- .../APIApprovals.Approve_API.approved.txt | 51 +++++++++++++++++++ .../APIApprovals.Approve_API.approved.txt | 51 +++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt b/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt index 4ed4d955..7e4ea181 100644 --- a/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt +++ b/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt @@ -46,6 +46,11 @@ namespace NServiceBus.IntegrationTesting public NServiceBus.IntegrationTesting.GenericHostEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } public NServiceBus.IntegrationTesting.GenericHostEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } } + public class HandlerInvocation : NServiceBus.IntegrationTesting.Invocation + { + public HandlerInvocation() { } + public System.Type HandlerType { get; set; } + } public interface IHandleTestCompletion { System.Threading.Tasks.Task OnTestCompleted(NServiceBus.AcceptanceTesting.Support.RunSummary summary); @@ -61,6 +66,7 @@ namespace NServiceBus.IntegrationTesting public System.Collections.Generic.IEnumerable InvokedHandlers { get; } public System.Collections.Generic.IEnumerable InvokedSagas { get; } public System.Collections.Generic.IEnumerable OutgoingMessageOperations { get; } + public System.Collections.Generic.IEnumerable RemoteSendMessageOperations { get; } public System.Collections.Generic.IReadOnlyDictionary GetProperties(string endpointName) { } public bool HandlerWasInvoked() { } public bool HasFailedMessages() { } @@ -74,6 +80,14 @@ namespace NServiceBus.IntegrationTesting public bool SagaWasInvoked() where TSaga : NServiceBus.Saga { } } + public abstract class Invocation + { + protected Invocation() { } + public string EndpointName { get; set; } + public System.Exception HandlingError { get; set; } + public object Message { get; set; } + public System.Type MessageType { get; set; } + } public class OutOfProcessEndpointBehaviorBuilder where TContext : NServiceBus.IntegrationTesting.IntegrationScenarioContext { @@ -86,6 +100,39 @@ namespace NServiceBus.IntegrationTesting public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } } + public abstract class OutgoingMessageOperation + { + protected OutgoingMessageOperation() { } + public System.Collections.Generic.Dictionary MessageHeaders { get; set; } + public string MessageId { get; set; } + public object MessageInstance { get; set; } + public System.Type MessageType { get; set; } + public System.Exception OperationError { get; set; } + public string SenderEndpoint { get; set; } + } + public class PublishOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation + { + public PublishOperation() { } + } + public class ReplyOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation + { + public ReplyOperation() { } + } + public class RequestTimeoutOperation : NServiceBus.IntegrationTesting.SendOperation + { + public RequestTimeoutOperation() { } + public string SagaId { get; set; } + public string SagaTypeAssemblyQualifiedName { get; set; } + } + public class SagaInvocation : NServiceBus.IntegrationTesting.Invocation + { + public SagaInvocation() { } + public bool IsCompleted { get; set; } + public bool IsNew { get; set; } + public bool NotFound { get; set; } + public object SagaData { get; set; } + public System.Type SagaType { get; set; } + } public static class ScenarioWithEndpointBehaviorExtensions { public static NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior WithGenericHostEndpoint(this NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior scenarioWithEndpoint, string endpointName, System.Func, Microsoft.Extensions.Hosting.IHost> hostBuilder, System.Action> behavior = null) @@ -93,4 +140,8 @@ namespace NServiceBus.IntegrationTesting public static NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior WithOutOfProcessEndpoint(this NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior scenarioWithEndpoint, string endpointName, NServiceBus.IntegrationTesting.EndpointReference endpointReference, System.Action> behavior = null) where TContext : NServiceBus.IntegrationTesting.IntegrationScenarioContext { } } + public class SendOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation + { + public SendOperation() { } + } } \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt b/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt index 4ed4d955..7e4ea181 100644 --- a/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt +++ b/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt @@ -46,6 +46,11 @@ namespace NServiceBus.IntegrationTesting public NServiceBus.IntegrationTesting.GenericHostEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } public NServiceBus.IntegrationTesting.GenericHostEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } } + public class HandlerInvocation : NServiceBus.IntegrationTesting.Invocation + { + public HandlerInvocation() { } + public System.Type HandlerType { get; set; } + } public interface IHandleTestCompletion { System.Threading.Tasks.Task OnTestCompleted(NServiceBus.AcceptanceTesting.Support.RunSummary summary); @@ -61,6 +66,7 @@ namespace NServiceBus.IntegrationTesting public System.Collections.Generic.IEnumerable InvokedHandlers { get; } public System.Collections.Generic.IEnumerable InvokedSagas { get; } public System.Collections.Generic.IEnumerable OutgoingMessageOperations { get; } + public System.Collections.Generic.IEnumerable RemoteSendMessageOperations { get; } public System.Collections.Generic.IReadOnlyDictionary GetProperties(string endpointName) { } public bool HandlerWasInvoked() { } public bool HasFailedMessages() { } @@ -74,6 +80,14 @@ namespace NServiceBus.IntegrationTesting public bool SagaWasInvoked() where TSaga : NServiceBus.Saga { } } + public abstract class Invocation + { + protected Invocation() { } + public string EndpointName { get; set; } + public System.Exception HandlingError { get; set; } + public object Message { get; set; } + public System.Type MessageType { get; set; } + } public class OutOfProcessEndpointBehaviorBuilder where TContext : NServiceBus.IntegrationTesting.IntegrationScenarioContext { @@ -86,6 +100,39 @@ namespace NServiceBus.IntegrationTesting public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } public NServiceBus.IntegrationTesting.OutOfProcessEndpointBehaviorBuilder When(System.Predicate condition, System.Func action) { } } + public abstract class OutgoingMessageOperation + { + protected OutgoingMessageOperation() { } + public System.Collections.Generic.Dictionary MessageHeaders { get; set; } + public string MessageId { get; set; } + public object MessageInstance { get; set; } + public System.Type MessageType { get; set; } + public System.Exception OperationError { get; set; } + public string SenderEndpoint { get; set; } + } + public class PublishOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation + { + public PublishOperation() { } + } + public class ReplyOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation + { + public ReplyOperation() { } + } + public class RequestTimeoutOperation : NServiceBus.IntegrationTesting.SendOperation + { + public RequestTimeoutOperation() { } + public string SagaId { get; set; } + public string SagaTypeAssemblyQualifiedName { get; set; } + } + public class SagaInvocation : NServiceBus.IntegrationTesting.Invocation + { + public SagaInvocation() { } + public bool IsCompleted { get; set; } + public bool IsNew { get; set; } + public bool NotFound { get; set; } + public object SagaData { get; set; } + public System.Type SagaType { get; set; } + } public static class ScenarioWithEndpointBehaviorExtensions { public static NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior WithGenericHostEndpoint(this NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior scenarioWithEndpoint, string endpointName, System.Func, Microsoft.Extensions.Hosting.IHost> hostBuilder, System.Action> behavior = null) @@ -93,4 +140,8 @@ namespace NServiceBus.IntegrationTesting public static NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior WithOutOfProcessEndpoint(this NServiceBus.AcceptanceTesting.Support.IScenarioWithEndpointBehavior scenarioWithEndpoint, string endpointName, NServiceBus.IntegrationTesting.EndpointReference endpointReference, System.Action> behavior = null) where TContext : NServiceBus.IntegrationTesting.IntegrationScenarioContext { } } + public class SendOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation + { + public SendOperation() { } + } } \ No newline at end of file From 977f9588ddef7b9796a232cb4e05a8f80319e0a3 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sun, 9 Jan 2022 14:31:22 +0100 Subject: [PATCH 50/52] Move gRpc types into a Grpc namespace --- src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs | 2 +- .../InterceptSendOperations.cs | 3 ++- .../RemoteEndpointServerV8.cs | 3 ++- .../OutOfProcessEndpointRunnerClient.cs | 1 + .../OutOfProcessEndpointRunnerImpl.cs | 3 ++- .../OutOfProcessEndpointRunnerServer.cs | 1 + .../RemoteEndpointClient.cs | 1 + .../RemoteEndpointImpl.cs | 3 ++- .../RemoteEndpointServer.cs | 1 + src/protos/OutOfProcessEndpointRunner.proto | 2 +- src/protos/RemoteEndpoint.proto | 2 +- 11 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs b/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs index 2da8966b..c635f37b 100644 --- a/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs +++ b/src/MySystem.AcceptanceTests/When_sending_ALonleyMessage.cs @@ -32,7 +32,7 @@ public async Task The_message_is_sent_as_expected() }) .Done(c => { - return c.RemoteSendMessageOperations.Any(op => op.MessageTypeAssemblyQualifiedName == typeof(ALonleyMessage).AssemblyQualifiedName) || c.HasFailedMessages(); + return c.RemoteOutgoingMessageOperations.Any(op => op.MessageTypeAssemblyQualifiedName == typeof(ALonleyMessage).AssemblyQualifiedName) || c.HasFailedMessages(); }) .Run(); diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs index 84118a99..6aed830b 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/InterceptSendOperations.cs @@ -1,4 +1,5 @@ -using NServiceBus.Pipeline; +using NServiceBus.IntegrationTesting.OutOfProcess.Grpc; +using NServiceBus.Pipeline; using System; using System.Linq; using System.Text.Json; diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs index ed700703..b279681e 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess.Nsb8/RemoteEndpointServerV8.cs @@ -1,4 +1,5 @@ -using System; +using NServiceBus.IntegrationTesting.OutOfProcess.Grpc; +using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerClient.cs index bbacedb7..87cac30e 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerClient.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerClient.cs @@ -1,4 +1,5 @@ using Grpc.Core; +using NServiceBus.IntegrationTesting.OutOfProcess.Grpc; using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerImpl.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerImpl.cs index b0451cab..444a605b 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerImpl.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerImpl.cs @@ -1,10 +1,11 @@ using Google.Protobuf.WellKnownTypes; using Grpc.Core; +using NServiceBus.IntegrationTesting.OutOfProcess.Grpc; using System; using System.Collections.Generic; using System.Text.Json; using System.Threading.Tasks; -using static NServiceBus.IntegrationTesting.OutOfProcess.OutOfProcessEndpointRunner; +using static NServiceBus.IntegrationTesting.OutOfProcess.Grpc.OutOfProcessEndpointRunner; namespace NServiceBus.IntegrationTesting.OutOfProcess { diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerServer.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerServer.cs index ef3fd923..51fc3d34 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerServer.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/OutOfProcessEndpointRunnerServer.cs @@ -1,4 +1,5 @@ using Grpc.Core; +using NServiceBus.IntegrationTesting.OutOfProcess.Grpc; using System; using System.Threading.Tasks; diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs index 2985b7a8..8bf7bff0 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointClient.cs @@ -1,4 +1,5 @@ using Grpc.Core; +using NServiceBus.IntegrationTesting.OutOfProcess.Grpc; using System.Text.Json; using System.Threading.Tasks; diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs index 93eea615..c2c5ab77 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointImpl.cs @@ -1,8 +1,9 @@ using Google.Protobuf.WellKnownTypes; using Grpc.Core; +using NServiceBus.IntegrationTesting.OutOfProcess.Grpc; using System; using System.Threading.Tasks; -using static NServiceBus.IntegrationTesting.OutOfProcess.RemoteEndpoint; +using static NServiceBus.IntegrationTesting.OutOfProcess.Grpc.RemoteEndpoint; namespace NServiceBus.IntegrationTesting.OutOfProcess { diff --git a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs index 1c5f9a22..f0d6afe4 100644 --- a/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs +++ b/src/NServiceBus.IntegrationTesting.OutOfProcess/RemoteEndpointServer.cs @@ -1,4 +1,5 @@ using Grpc.Core; +using NServiceBus.IntegrationTesting.OutOfProcess.Grpc; using System; using System.Threading.Tasks; diff --git a/src/protos/OutOfProcessEndpointRunner.proto b/src/protos/OutOfProcessEndpointRunner.proto index a359d8d3..a8d55873 100644 --- a/src/protos/OutOfProcessEndpointRunner.proto +++ b/src/protos/OutOfProcessEndpointRunner.proto @@ -3,7 +3,7 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; import "google/protobuf/any.proto"; -option csharp_namespace = "NServiceBus.IntegrationTesting.OutOfProcess"; +option csharp_namespace = "NServiceBus.IntegrationTesting.OutOfProcess.Grpc"; package NServiceBus.IntegrationTesting.TestRunner; service OutOfProcessEndpointRunner { diff --git a/src/protos/RemoteEndpoint.proto b/src/protos/RemoteEndpoint.proto index 11f357da..96eeea0f 100644 --- a/src/protos/RemoteEndpoint.proto +++ b/src/protos/RemoteEndpoint.proto @@ -2,7 +2,7 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; -option csharp_namespace = "NServiceBus.IntegrationTesting.OutOfProcess"; +option csharp_namespace = "NServiceBus.IntegrationTesting.OutOfProcess.Grpc"; package NServiceBus.IntegrationTesting.RemoteEndpoint; service RemoteEndpoint { From fe8ddb1eb83699efe4c8e25e19f8925899d90f93 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sun, 9 Jan 2022 14:32:07 +0100 Subject: [PATCH 51/52] Create a wrapper type to avoid exposing users directly to gRpc types --- .../IntegrationScenarioContext.cs | 18 ++++++++---- .../RemoteOutgoingMessages.cs | 28 +++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 src/NServiceBus.IntegrationTesting/RemoteOutgoingMessages.cs diff --git a/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs b/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs index 8c6e971a..f80e251d 100644 --- a/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs +++ b/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs @@ -1,10 +1,9 @@ using NServiceBus.AcceptanceTesting; using NServiceBus.DelayedDelivery; -using NServiceBus.IntegrationTesting.OutOfProcess; +using NServiceBus.IntegrationTesting.OutOfProcess.Grpc; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; namespace NServiceBus.IntegrationTesting @@ -15,7 +14,7 @@ public class IntegrationScenarioContext : ScenarioContext readonly ConcurrentBag invokedHandlers = new(); readonly ConcurrentBag invokedSagas = new(); readonly ConcurrentBag outgoingMessageOperations = new(); - readonly ConcurrentBag remoteSendMessageOperations = new(); + readonly ConcurrentBag remoteOutgoingMessageOperations = new(); readonly Dictionary> timeoutRescheduleRules = new(); readonly Dictionary> properties = new(); readonly Dictionary ports = new(); @@ -46,12 +45,21 @@ public class IntegrationScenarioContext : ScenarioContext internal void AddRemoteOperation(RemoteSendMessageOperation operation) { - remoteSendMessageOperations.Add(operation); + remoteOutgoingMessageOperations.Add(new RemoteSendOperation + { + SenderEndpoint = operation.SenderEndpoint, + MessageId = operation.MessageId, + MessageInstanceJson = operation.MessageInstanceJson, + MessageTypeAssemblyQualifiedName = operation.MessageTypeAssemblyQualifiedName, + MessageHeaders = operation.MessageHeaders.ToDictionary(x => x.Key, x => x.Value), + OperationErrorJson = operation.OperationErrorJson, + OperationErrorTypeAssemblyQualifiedName = operation.OperationErrorTypeAssemblyQualifiedName + }); } public IEnumerable InvokedSagas { get { return invokedSagas; } } public IEnumerable OutgoingMessageOperations { get { return outgoingMessageOperations; } } - public IEnumerable RemoteSendMessageOperations { get { return remoteSendMessageOperations; } } + public IEnumerable RemoteOutgoingMessageOperations { get { return remoteOutgoingMessageOperations; } } public IReadOnlyDictionary GetProperties(string endpointName) { diff --git a/src/NServiceBus.IntegrationTesting/RemoteOutgoingMessages.cs b/src/NServiceBus.IntegrationTesting/RemoteOutgoingMessages.cs new file mode 100644 index 00000000..3f7ee9c2 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/RemoteOutgoingMessages.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace NServiceBus.IntegrationTesting +{ + public abstract class RemoteOutgoingMessageOperation + { + public string SenderEndpoint { get; set; } + public string MessageId { get; set; } + public string MessageTypeAssemblyQualifiedName { get; set; } + public string MessageInstanceJson { get; set; } + public Dictionary MessageHeaders { get; set; } + public string OperationErrorJson { get; set; } + public string OperationErrorTypeAssemblyQualifiedName { get; set; } + } + + public class RemoteSendOperation : RemoteOutgoingMessageOperation { } + + public class RemoteReplyOperation : RemoteOutgoingMessageOperation { } + + public class RemotePublishOperation : RemoteOutgoingMessageOperation { } + + public class RemoteRequestTimeoutOperation : RemoteSendOperation + { + public string SagaId { get; set; } + public string SagaTypeAssemblyQualifiedName { get; set; } + } +} From 520c488af0832c53c457a619593b45f9c782c453 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sun, 9 Jan 2022 14:32:15 +0100 Subject: [PATCH 52/52] Approved API --- .../APIApprovals.Approve_API.approved.txt | 31 ++++++++++++++++++- .../APIApprovals.Approve_API.approved.txt | 31 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt b/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt index 7e4ea181..7494d5dd 100644 --- a/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt +++ b/src/NServiceBus.IntegrationTesting.Tests/API/NET48/APIApprovals.Approve_API.approved.txt @@ -66,7 +66,7 @@ namespace NServiceBus.IntegrationTesting public System.Collections.Generic.IEnumerable InvokedHandlers { get; } public System.Collections.Generic.IEnumerable InvokedSagas { get; } public System.Collections.Generic.IEnumerable OutgoingMessageOperations { get; } - public System.Collections.Generic.IEnumerable RemoteSendMessageOperations { get; } + public System.Collections.Generic.IEnumerable RemoteOutgoingMessageOperations { get; } public System.Collections.Generic.IReadOnlyDictionary GetProperties(string endpointName) { } public bool HandlerWasInvoked() { } public bool HasFailedMessages() { } @@ -114,6 +114,35 @@ namespace NServiceBus.IntegrationTesting { public PublishOperation() { } } + public abstract class RemoteOutgoingMessageOperation + { + protected RemoteOutgoingMessageOperation() { } + public System.Collections.Generic.Dictionary MessageHeaders { get; set; } + public string MessageId { get; set; } + public string MessageInstanceJson { get; set; } + public string MessageTypeAssemblyQualifiedName { get; set; } + public string OperationErrorJson { get; set; } + public string OperationErrorTypeAssemblyQualifiedName { get; set; } + public string SenderEndpoint { get; set; } + } + public class RemotePublishOperation : NServiceBus.IntegrationTesting.RemoteOutgoingMessageOperation + { + public RemotePublishOperation() { } + } + public class RemoteReplyOperation : NServiceBus.IntegrationTesting.RemoteOutgoingMessageOperation + { + public RemoteReplyOperation() { } + } + public class RemoteRequestTimeoutOperation : NServiceBus.IntegrationTesting.RemoteSendOperation + { + public RemoteRequestTimeoutOperation() { } + public string SagaId { get; set; } + public string SagaTypeAssemblyQualifiedName { get; set; } + } + public class RemoteSendOperation : NServiceBus.IntegrationTesting.RemoteOutgoingMessageOperation + { + public RemoteSendOperation() { } + } public class ReplyOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation { public ReplyOperation() { } diff --git a/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt b/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt index 7e4ea181..7494d5dd 100644 --- a/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt +++ b/src/NServiceBus.IntegrationTesting.Tests/API/NETCOREAPP/APIApprovals.Approve_API.approved.txt @@ -66,7 +66,7 @@ namespace NServiceBus.IntegrationTesting public System.Collections.Generic.IEnumerable InvokedHandlers { get; } public System.Collections.Generic.IEnumerable InvokedSagas { get; } public System.Collections.Generic.IEnumerable OutgoingMessageOperations { get; } - public System.Collections.Generic.IEnumerable RemoteSendMessageOperations { get; } + public System.Collections.Generic.IEnumerable RemoteOutgoingMessageOperations { get; } public System.Collections.Generic.IReadOnlyDictionary GetProperties(string endpointName) { } public bool HandlerWasInvoked() { } public bool HasFailedMessages() { } @@ -114,6 +114,35 @@ namespace NServiceBus.IntegrationTesting { public PublishOperation() { } } + public abstract class RemoteOutgoingMessageOperation + { + protected RemoteOutgoingMessageOperation() { } + public System.Collections.Generic.Dictionary MessageHeaders { get; set; } + public string MessageId { get; set; } + public string MessageInstanceJson { get; set; } + public string MessageTypeAssemblyQualifiedName { get; set; } + public string OperationErrorJson { get; set; } + public string OperationErrorTypeAssemblyQualifiedName { get; set; } + public string SenderEndpoint { get; set; } + } + public class RemotePublishOperation : NServiceBus.IntegrationTesting.RemoteOutgoingMessageOperation + { + public RemotePublishOperation() { } + } + public class RemoteReplyOperation : NServiceBus.IntegrationTesting.RemoteOutgoingMessageOperation + { + public RemoteReplyOperation() { } + } + public class RemoteRequestTimeoutOperation : NServiceBus.IntegrationTesting.RemoteSendOperation + { + public RemoteRequestTimeoutOperation() { } + public string SagaId { get; set; } + public string SagaTypeAssemblyQualifiedName { get; set; } + } + public class RemoteSendOperation : NServiceBus.IntegrationTesting.RemoteOutgoingMessageOperation + { + public RemoteSendOperation() { } + } public class ReplyOperation : NServiceBus.IntegrationTesting.OutgoingMessageOperation { public ReplyOperation() { }